1 #
   2 # Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
   3 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4 # 
   5 # This code is free software; you can redistribute it and/or modify it
   6 # under the terms of the GNU General Public License version 2 only, as
   7 # published by the Free Software Foundation.
   8 # 
   9 # This code is distributed in the hope that it will be useful, but WITHOUT
  10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11 # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12 # version 2 for more details (a copy is included in the LICENSE file that
  13 # accompanied this code).
  14 # 
  15 # You should have received a copy of the GNU General Public License version
  16 # 2 along with this work; if not, write to the Free Software Foundation,
  17 # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18 # 
  19 # Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20 # or visit www.oracle.com if you need additional information or have any
  21 # questions.
  22 #
  23 
  24 
  25 # JDK changeset checker
  26 
  27 # Quick configuration: Add the following to your ~/.hgrc:
  28 #
  29 #   [extensions]
  30 #   jcheck = /path/to/jcheck.py
  31 #
  32 #   # Omit these lines if you use Mercurial Queues
  33 #   [hooks]
  34 #   pretxnchangegroup.jcheck = python:jcheck.hook
  35 #   pretxncommit.jcheck = python:jcheck.hook
  36 #
  37 #   # Include this if you use the (deprecated) Mercurial "fetch" extension
  38 #   [defaults]
  39 #   fetch = -m Merge
  40 #
  41 # For more information: http://openjdk.java.net/projects/code-tools/jcheck/
  42 
  43 _version = "@VERSION@"
  44 _date = "@DATE@"
  45 
  46 import sys, os, re, urllib, urllib2
  47 from mercurial.node import *
  48 from mercurial import cmdutil, patch, util, context, templater
  49 
  50 Pass = False
  51 Fail = True
  52 
  53 def datestr(ctx):
  54     # Mercurial 0.9.5 and earlier append a time zone; strip it.
  55     return util.datestr(ctx.date(), format="%Y-%m-%d %H:%M")[:16]
  56     
  57 def oneline(ctx):
  58     return ("%5d:%s  %-12s  %s  %s\n"
  59             % (ctx.rev(), short(ctx.node()), ctx.user(), datestr(ctx),
  60                ctx.description().splitlines()[0]))
  61 
  62 def is_merge(repo, rev):
  63     return not (-1 in repo.changelog.parentrevs(rev))
  64 
  65 _matchall = getattr(cmdutil, 'matchall', None)
  66 if not _matchall:
  67     try:
  68         from mercurial import scmutil
  69         _matchall = scmutil.matchall
  70     except ImportError:
  71         pass
  72 
  73 def repocompat(repo):
  74     # Modern mercurial versions use len(repo) and repo[cset_id]; enable those
  75     # operations with older versions.
  76     t = type(repo)
  77     if not getattr(t, '__len__', None):
  78         def repolen(self):
  79             return self.changelog.count()
  80         setattr(t, '__len__', repolen)
  81     if not getattr(t, '__getitem__', None):
  82         def repoitem(self, arg):
  83             return context.changectx(self, arg)
  84         setattr(t, '__getitem__', repoitem)
  85     # Similarly, use branchmap instead of branchtags; enable it if needed.
  86     if not getattr(t, 'branchmap', None):
  87         setattr(t, 'branchmap', t.branchtags)
  88 
  89 
  90 # Configuration-file parsing
  91 
  92 def load_conf(root):
  93     cf = { }
  94     fn = os.path.join(root, ".jcheck/conf")
  95     f = open(fn)
  96     try:
  97         prop_re = re.compile("\s*(\S+)\s*=\s*(\S+)\s*$")
  98         i = 0
  99         for ln in f.readlines():
 100             i = i + 1
 101             ln = ln.strip()
 102             if (ln.startswith("#")):
 103                 continue
 104             m = prop_re.match(ln)
 105             if not m:
 106                 raise util.Abort("%s:%d: Invalid configuration syntax: %s"
 107                                  % (fn, i, ln))
 108             cf[m.group(1)] = m.group(2)
 109     finally:
 110         f.close()
 111     for pn in ["project"]:
 112         if not cf.has_key(pn):
 113             raise util.Abort("%s: Missing property: %s" % (fn, pn))
 114     return cf
 115 
 116 
 117 # Author validation
 118 
 119 author_cache = { }                      ## Should really cache more permanently
 120 
 121 def validate_author(an, pn):
 122   if author_cache.has_key(an):
 123     return True
 124   u = ("http://db.openjdk.java.net/people/%s/projects/%s"
 125        % (urllib.quote(an), pn))
 126   f = None
 127   try:
 128       try:
 129           f = urllib2.urlopen(u)
 130       except urllib2.HTTPError, e:
 131           if e.code == 404:
 132               return False
 133           raise e
 134   finally:
 135       if f:
 136           f.close()
 137   author_cache[an] = True
 138   return True
 139 
 140 
 141 # Whitespace and comment validation
 142 
 143 badwhite_re = re.compile("(\t)|([ \t]$)|\r", re.MULTILINE)
 144 normext_re = re.compile(".*\.(java|c|h|cpp|hpp)$")
 145 
 146 tag_desc_re = re.compile("Added tag [^ ]+ for changeset [0-9a-f]{12}")
 147 tag_re = re.compile("tip$|jdk[4-9](u\d{1,3})?-b\d{2,3}$|hs\d\d(\.\d{1,2})?-b\d\d$")
 148 
 149 def badwhite_what(m):
 150     if m.group(1):
 151         return "Tab character"
 152     if m.group(2):
 153         return "Trailing whitespace"
 154     return "Carriage return (^M)"
 155 
 156 base_addr_pat = "[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}"
 157 addr_pat = ("(" + base_addr_pat + ")"
 158             + "|(([-_a-zA-Z0-9][-_ a-zA-Z0-9]+) +<" + base_addr_pat + ">)")
 159 
 160 bug_ident = re.compile("(([A-Z][A-Z0-9]+-)?[0-9]+):")
 161 bug_check = re.compile("([0-9]{7}): \S.*$")
 162 sum_ident = re.compile("Summary:")
 163 sum_check = re.compile("Summary: \S.*")
 164 rev_ident = re.compile("Reviewed-by:")
 165 rev_check = re.compile("Reviewed-by: (([a-z0-9]+)(, [a-z0-9]+)*$)")
 166 con_ident = re.compile("Contributed-by:")
 167 con_check = re.compile("Contributed-by: ((" + addr_pat + ")(, (" + addr_pat + "))*)$")
 168 
 169 def bug_validate(ch, ctx, m, pn):
 170     bs = m.group(1)
 171     if not (bs[0] in ['1','2','4','5','6','7','8']):
 172         ch.error(ctx, "Invalid bugid: %s" % bs)
 173     b = int(bs)
 174     if b in ch.cs_bugids:
 175         ch.error(ctx, "Bugid %d used more than once in this changeset" % b)
 176     ch.cs_bugids.append(b)
 177     if not ch.bugids_allow_dups and b in ch.repo_bugids:
 178         r = ch.repo_bugids[b]
 179         if r < ctx.rev():
 180             ch.error(ctx, ("Bugid %d already used in this repository, in revision %d "
 181                            % (b, r)))
 182 
 183 def rev_validate(ch, ctx, m, pn):
 184     ans = re.split(", *", m.group(1))
 185     for an in ans:
 186         if not validate_author(an, pn):
 187             ch.error(ctx, "Invalid reviewer name: %s" % an)
 188         ch.cs_reviewers.append(an)
 189 
 190 def con_validate(ch, ctx, m, pn):
 191     ch.cs_contributor = m.group(1)
 192 
 193 class State:
 194     def __init__(self, name, ident_pattern, check_pattern,
 195                  validator=None, min=0, max=1):
 196         self.name = name
 197         self.ident_pattern = ident_pattern
 198         self.check_pattern = check_pattern
 199         self.validator = validator
 200         self.min = min
 201         self.max = max
 202 
 203 comment_grammar = [
 204     State("bugid line",
 205           bug_ident, bug_check, validator=bug_validate, min=1, max=1000),
 206     State("change summary",
 207           sum_ident, sum_check, min=0, max=1),
 208     State("reviewer attribution",
 209           rev_ident, rev_check, validator=rev_validate, min=1, max=1),
 210     State("contributor attribution",
 211           con_ident, con_check, validator=con_validate, min=0, max=1)
 212 ]
 213 
 214 def checked_comment_line(ln):
 215     for st in comment_grammar:
 216         if st.ident_pattern.match(ln):
 217             return True
 218     return False
 219 
 220 def repo_bugids(ui, repo):
 221     def addbugids(bugids, ctx):
 222         lns = ctx.description().splitlines()
 223         for ln in lns:
 224             m = bug_check.match(ln)
 225             if m:
 226                 b = int(m.group(1))
 227                 if not b in bugids:
 228                     bugids[b] = ctx.rev()
 229         
 230     # Should cache this, eventually
 231     bugids = { }                        # bugid -> rev
 232     opts = { 'rev' : ['0:tip'] }
 233     try:
 234         nop = lambda c, fns: None
 235         iter = cmdutil.walkchangerevs(repo, _matchall(repo), opts, nop)
 236         for ctx in iter:
 237             addbugids(bugids, ctx)
 238     except (AttributeError, TypeError):
 239         # AttributeError:  matchall does not exist in hg < 1.1
 240         # TypeError:  walkchangerevs args differ in hg <= 1.3.1
 241         get = util.cachefunc(lambda r: repo.changectx(r).changeset())
 242         changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, opts)
 243         for st, rev, fns in changeiter:
 244             if st == 'add':
 245                 node = repo.changelog.node(rev)
 246                 addbugids(bugids, context.changectx(repo, node))
 247     if ui.debugflag:
 248         ui.debug("Bugids: %s\n" % bugids)
 249     return bugids
 250 
 251 
 252 
 253 # Black/white lists
 254 ## The black/white lists should really be in the database
 255 
 256 # Bogus yet historically-accepted changesets,
 257 # so that jcheck may evolve
 258 #
 259 changeset_whitelist = [
 260     '31000d79ec713de1e601dc16d74d726edd661ed5',
 261     'b7987d19f5122a9f169e568f935b7cdf1a2609f5',
 262     'c70a245cad3ad74602aa26b9d8e3d0472f7317c3',
 263     'e8e20316458c1cdb85d9733a2e357e438a76a859',
 264     'f68325221ce1efe94ab367400a49a8039d9b3db3',
 265     '4dfa5d67c44500155ce9ab1e00d0de21bdbb9ee6',
 266     '73a4d5be86497baf74c1fc194c9a0dd4e86d3a31', # jdk6/jdk6/jaxp bad comment
 267     'a25f15bfd04b46a302b6ca1a298c176344f432dd', # jdk6/jdk6/jdk  bad comment
 268     'bf87d5af43614d609a5251c43eea44c028500d02', # jdk6/jdk6/jdk  bad comment
 269     'd77434402021cebc4c25b452db18bbfd2d7ccda1', # jdk6/jdk6/jdk  bad comment
 270     '931e5f39e365a0d550d79148ff87a7f9e864d2e1', # hotspot dup bug id 7147064
 271     'd8abc90163a4b58db407a60cba331ab21c9977e7', # hotspot dup bug id 7147064
 272     '45849c62c298aa8426c9e67599e4e35793d8db13', # pubs executable files
 273     '38050e6655d8acc220800a28128cef328906e825', # pubs invalid bugid line
 274     # hotspot/test/closed no Reviewed-by line
 275     '8407fef5685f32ed42b79c5d5f13f6c8007171ac',
 276     ]
 277 
 278 # Bad changesets that should never be allowed in
 279 #
 280 changeset_blacklist = [
 281     'd3c74bae36884525be835ea428293bb6e7fa54d3',
 282     '2bb5ef5c8a2dc0a32b1cd67803128ce12cad461e',
 283     '4ff95cec682e67a374f9c5725d1879f43624d888',
 284     '75f1884152db275047a09aa6085ae7c49e3f4126',
 285     '6ecad8bfb1e5d34aef2ca61d30c1d197745d6844',
 286     '4ec7d1890538c54a3cc7559e88db5a5e3371fe5d',
 287     '669768c591ac438f4ca26d5cbdde7486ce49a2e2',
 288     '2ded3bb1452943d5273e7b83af9609ce6511a105',
 289     # hsdev/hotspot/{hotspot,master} dup bugid 7019157
 290     '0d8777617a2d028ba0b82943c829a5c6623f1479',
 291     # hsx/hotspot-comp/jdk dup bugid 7052202 + follow-on cset
 292     'ad2d483067099421f3ea4492269cce69009b046f',
 293     '521e2254994c76441c25f4374e16abbe314d8143',
 294     # hsx/hotspot-rt/hotspot wrong bugid 7059288 + associated merge
 295     'aa5f3f5978991182b8dbbd2b46fdcb47b6371dd9',
 296     '709d9389b2bc290dad5a35ec5b5f951b07ce9631',
 297     # jdk8/awt/jdk dup bugid 7100054
 298     'f218e6bdf1e8e20ca3f0fdaeb29c38f56afdf988',
 299     # jdk7u/jdk7u-dev/jaxp mistaken push
 300     '24f4c1185305b60818d255550a0fdc1ddf52c2a6',
 301     # jdk8/build/pubs executable file
 302     '2528f2a1117000eb98891a139e8f839fc5e2bfab',
 303     # jdk8/2d/jdk/src/closed security fix in wrong forest
 304     '8c7fbf082af5ec6d7ad0b1789cedc98a597f1f83',
 305     # jdk7u/jdk7u5/jdk bad fix for 6648202
 306     'b06f6d9a6c329792401b954682d49169484b586d',
 307     # hsx/hsx24/hotspot/src/closed bad merge
 308     '306614eb47a79e6d25a8c7447d0fe47fac28b24c',
 309     # hsx/hsx24/hotspot/test/closed bad merge
 310     '96163ee390bf223fe0037739fc953e8ed7d49560',
 311     # jdk8/awt/jdk INTJDK-7600365
 312     '6be9b0bda6dccbfc738b9173a71a15dcafda4f3b',
 313     # jdk8/tl/jdk/test/closed INTJDK-7600460
 314     '9eb97a2b3274edff83a362f76bbadb866a97a89b',
 315     # jdk7u11-dev/jaxp bad fix for 7192390
 316     '1642814c94fd0206f8b4f460cc77fa6fc099731a',
 317     # jdk7u11-dev/jdk bad fix for 7192390
 318     '90eb0407ca69dc572d20490f17239b183bb616b6',
 319     # jdk7u11-dev/jdk/test/closed bad fix for 7192390
 320     '512af24c6909ef2c766c3a5217c719545de68bf7',
 321     # jdk7u11-dev/jdk redone rmi fix
 322     'fd6ce0200a7f519380e6863930e92f9182030fa0',
 323     # jdk7u11-dev/jdk/test/closed redone rmi fix
 324     '770d9cf0e1dc97f7aaa3fdfbb430b27a40b1a3a9',
 325     # jdk7u13-dev/jdk bad fix for 8006611
 326     '12b6a43f9fefa6c3bbf81d9096e764e72bacf065',
 327     # jdk8/nashorn unwanted tag jdk8-b78
 328     '8f49d8121c7e63d22f55412df4ff4800599686d6',
 329     # hsx/hotspot-emb/hotspot wrong bugid 8009004
 330     '29ab68ef5bb643f96218126dc2ff845561d552a4',
 331     # jdk7u40/jdk/src/closed mistaken push 8016315
 332     'd2b0a0c38c808bddff604a025469c5102a62edfe',
 333     # jdk7u40/jdk/test/closed mistaken push 8016622
 334     'd1e0d129aa0fccdc1ff1bcf20f126ffea900f30b',
 335     # hsx/hotspot-gc/hotspot wrong bugid 8024547
 336     '9561e0a8a2d63c45e751755d791e25396d94025a',
 337     # jdk8/ds/install dup bugid 8024771
 338     '835ef04a36e5fe95d4c0deb6e633371053f3cbba',
 339     # jdk5u/jdk5.0u55/j2se bad fix 8025034
 340     'e18574aa4be397b83a43e1444cb96c903f152fcb',
 341     # jdk6u/jdk6u65/j2se bad fix 8025034
 342     'a0f1589decc6181a5e048e48058d12cfa68cd3e1',
 343     # jdk7u/jdk7u45/j2se bad fix 8025034
 344     '0a312c04dbc8d33601efecfb0d23b8c09bf088fe',
 345     # jdk8/build/pubs executable files
 346     '3ecd3336c805978a37a933fbeca26c65fbe81432',
 347     # hsx/jdk7u/hotspot wrong bugid
 348     'f5d8e6d72e23d972db522f7ad4cd3b9b01085466',
 349     # jdk8/tl/jdk erroneous push 7152892
 350     'da4b0962ad1161dbd84e7daa0fdc706281c456a2',
 351     # jdk8/tl/jdk/test/closed erroneous push 7152892
 352     '1e69a1ce212c7c4c884f155dd123c936787db273',
 353     # jdk9/jdk9/closed bad tag
 354     '61fdebb503d79392536b8f502ae215022d1a1f1c',
 355     # jdk9/hs-rt/jdk/src/closed dup bugid 8034951
 356     'a19596796430761dde87bee9f6616480f1c93678',
 357     # jdk9/hs-rt/jdk/test/closed dup bugid 8034951
 358     'd2308c9714c94e87a0e60cda314746a5c17dbcc2',
 359     # jdk9/client/deploy erroneous push 8041798
 360     'fff4ff4fd6f031ab335b44842d69fd125297b5ab',
 361     ]
 362 
 363 # Path to file containing additional blacklisted changesets
 364 blacklist_file = '/oj/db/hg/blacklist'
 365 
 366 
 367 # Checker class
 368 
 369 class checker(object):
 370 
 371     def __init__(self, ui, repo, strict, lax):
 372         self.ui = ui
 373         self.repo = repo
 374         self.rv = Pass
 375         self.checks = [c for c in checker.__dict__ if c.startswith("c_")]
 376         self.checks.sort()
 377         self.summarized = False
 378         self.repo_bugids = [ ]
 379         self.cs_bugids = [ ]            # Bugids in current changeset
 380         self.cs_author = None           # Author of current changeset
 381         self.cs_reviewers = [ ]         # Reviewers of current changeset
 382         self.cs_contributor = None      # Contributor of current changeset
 383         self.strict = strict
 384         self.conf = load_conf(repo.root)
 385         self.whitespace_lax = lax and not strict
 386         if self.conf.get("whitespace") == "lax":
 387             self.whitespace_lax = True
 388         self.comments_lax = lax and not strict
 389         if self.conf.get("comments") == "lax":
 390             self.comments_lax = True
 391         self.tags_lax = lax and not strict
 392         if self.conf.get("tags") == "lax":
 393             self.tags_lax = True
 394         self.bugids_allow_dups = self.conf.get("bugids") == "dup"
 395         self.bugids_lax = lax and not strict
 396         if self.conf.get("bugids") == "lax":
 397             self.bugids_lax = True
 398         self.bugids_ignore = False
 399         if self.conf.get("bugids") == "ignore":
 400             self.bugids_ignore = True
 401         if not self.bugids_ignore:
 402             # only identify bug ids if we are going to use them
 403             self.repo_bugids = repo_bugids(ui, repo)
 404         self.blacklist = dict.fromkeys(changeset_blacklist)
 405         self.read_blacklist(blacklist_file)
 406         # hg < 1.0 does not have localrepo.tagtype()
 407         self.tagtype = getattr(self.repo, 'tagtype', lambda k: 'global')
 408 
 409     def read_blacklist(self, fname):
 410         if not os.path.exists(fname):
 411             return
 412         self.ui.debug('Reading blacklist file %s\n' % fname)
 413         f = open(fname)
 414         for line in f:
 415             # Any comment after the changeset hash becomes the dictionary value.
 416             l = [s.strip() for s in line.split('#', 1)]
 417             if l and l[0]:
 418                 self.blacklist[l[0]] = len(l) == 2 and l[1] or None
 419         f.close()
 420 
 421     def summarize(self, ctx):
 422         self.ui.status("\n")
 423         self.ui.status("> Changeset: %d:%s\n" % (ctx.rev(), short(ctx.node())))
 424         self.ui.status("> Author:    %s\n" % ctx.user())
 425         self.ui.status("> Date:      %s\n" % datestr(ctx))
 426         self.ui.status(">\n> ")
 427         self.ui.status("\n> ".join(ctx.description().splitlines()))
 428         self.ui.status("\n\n")
 429 
 430     def error(self, ctx, msg):
 431         if self.rv != Fail:
 432             self.ui.status("[jcheck %s %s]\n" % (_version, _date))
 433         if not self.summarized:
 434             if ctx:
 435                 self.summarize(ctx)
 436             else:
 437                 self.ui.status("\n")
 438             self.summarized = True
 439         self.ui.status(msg + "\n")
 440         self.rv = Fail
 441 
 442     def c_00_author(self, ctx):
 443         self.ui.debug("author: %s\n" % ctx.user())
 444         if not validate_author(ctx.user(), self.conf["project"]):
 445             self.error(ctx, "Invalid changeset author: %s" % ctx.user())
 446         self.cs_author = ctx.user()
 447 
 448     def c_01_comment(self, ctx):
 449         m = badwhite_re.search(ctx.description())
 450         if m:
 451             ln = ctx.description().count("\n", 0, m.start()) + 1
 452             self.error(ctx, "%s in comment (line %d)" % (badwhite_what(m), ln))
 453 
 454         if is_merge(self.repo, ctx.rev()):
 455             if ctx.description() != "Merge":
 456                 self.error(ctx, ("Invalid comment for merge changeset"
 457                                  + " (must be \"Merge\")"))
 458             return
 459 
 460         if tag_desc_re.match(ctx.description()):
 461             ## Should check tag itself
 462             return
 463 
 464         if ((ctx.rev() == 0 or (ctx.rev() == 1 and self.comments_lax))
 465             and ctx.user() == "duke"
 466             and ctx.description().startswith("Initial load")):
 467             return
 468 
 469         lns = ctx.description().splitlines()
 470 
 471         # If lax, filter out non-matching lines
 472         if self.comments_lax:
 473             lns = filter(checked_comment_line, lns)
 474 
 475         i = 0                           # Input index
 476         gi = -1                         # Grammar index
 477         n = 0                           # Occurrence count
 478         while i < len(lns):
 479             gi = gi + 1
 480             if gi >= len(comment_grammar):
 481                 break
 482             ln = lns[i]
 483             st = comment_grammar[gi]
 484             n = 0
 485             while (st.ident_pattern.match(ln)):
 486                 m = st.check_pattern.match(ln)
 487                 if not m:
 488                     if not (st.name == "bugid line" and (self.bugids_lax or self.bugids_ignore)):
 489                         self.error(ctx, "Invalid %s" % st.name)
 490                 elif st.validator:
 491                     if not (st.name == "bugid line" and self.bugids_ignore):
 492                         st.validator(self, ctx, m, self.conf["project"])
 493                 n = n + 1
 494                 i = i + 1
 495                 if i >= len(lns):
 496                     break;
 497                 ln = lns[i]
 498             if n < st.min and not self.comments_lax:
 499                 self.error(ctx, "Incomplete comment: Missing %s" % st.name)
 500             if n > st.max:
 501                 self.error(ctx, "Too many %ss" % st.name)
 502 
 503         if not self.cs_contributor and [self.cs_author] == self.cs_reviewers:
 504             self.error(ctx, "Self-reviews not permitted")
 505         if not self.comments_lax:
 506             if (gi == 0 and n > 0):
 507                 self.error(ctx, "Incomplete comment: Missing bugid line")
 508             elif gi == 1 or (gi == 2 and n == 0):
 509                 self.error(ctx, "Incomplete comment: Missing reviewer attribution")
 510             if (i < len(lns)):
 511                 self.error(ctx, "Extraneous text in comment")
 512 
 513     def c_02_files(self, ctx):
 514         changes = self.repo.status(ctx.parents()[0].node(),
 515                                    ctx.node(), None)[:5]
 516         modified, added = changes[:2]
 517         # ## Skip files that were renamed but not modified
 518         files = modified + added
 519         if self.ui.debugflag:
 520             self.ui.debug("Checking files: %s\n" % ", ".join(files))
 521         for f in files:
 522             if ctx.rev() == 0:
 523                 ## This is loathsome
 524                 if f.startswith("test/java/rmi"): continue
 525                 if f.startswith("test/com/sun/javadoc/test"): continue
 526                 if f.startswith("docs/technotes/guides"): continue
 527             fx = ctx.filectx(f)
 528             if normext_re.match(f) and not self.whitespace_lax:
 529                 data = fx.data()
 530                 m = badwhite_re.search(data)
 531                 if m:
 532                     ln = data.count("\n", 0, m.start()) + 1
 533                     self.error(ctx, "%s:%d: %s" % (f, ln, badwhite_what(m)))
 534             ## check_file_header(self, fx, data)
 535             flags = fx.manifest().flags(f)
 536             if 'x' in flags:
 537                 self.error(ctx, "%s: Executable files not permitted" % f)
 538             if 'l' in flags:
 539                 self.error(ctx, "%s: Symbolic links not permitted" % f)
 540 
 541     def c_03_hash(self, ctx):
 542         hash = hex(ctx.node())
 543         if hash in self.blacklist:
 544             self.error(ctx, "Blacklisted changeset: " + hash)
 545 
 546     def check(self, node):
 547         self.summarized = False
 548         self.cs_bugids = [ ]
 549         self.cs_author = None
 550         self.cs_reviewers = [ ]
 551         self.cs_contributor = None
 552         ctx = context.changectx(self.repo, node)
 553         self.ui.note(oneline(ctx))
 554         if hex(node) in changeset_whitelist:
 555             self.ui.note("%s in whitelist; skipping\n" % hex(node))
 556             return Pass
 557         for c in self.checks:
 558             cf = checker.__dict__[c]
 559             cf(self, ctx)
 560         return self.rv
 561 
 562     def check_repo(self):
 563 
 564         if not self.tags_lax:
 565             ts = self.repo.tags().keys()
 566             ignoredtypes = ['local']
 567             for t in ts:
 568                 if not tag_re.match(t) and not self.tagtype(t) in ignoredtypes:
 569                     self.error(None,
 570                                "Illegal tag name: %s" % t)
 571 
 572         bs = self.repo.branchmap()
 573         if len(bs) > 1:
 574             bs = bs.copy()
 575             del bs["default"]
 576             self.error(None,
 577                        "Named branches not permitted; this repository has: %s"
 578                        % ", ".join(bs.keys()))
 579 
 580         if self.strict:
 581             nh = len(self.repo.heads())
 582             if nh > 1:
 583                 self.error(None,
 584                            "Multiple heads not permitted; this repository has %d"
 585                            % nh)
 586 
 587         return self.rv
 588 
 589 
 590 def hook(ui, repo, hooktype, node=None, source=None, **opts):
 591     ui.debug("jcheck: node %s, source %s, args %s\n" % (node, source, opts))
 592     repocompat(repo)
 593     if not repo.local():
 594         raise util.Abort("repository '%s' is not local" % repo.path)
 595     if not os.path.exists(os.path.join(repo.root, ".jcheck")):
 596         ui.note("jcheck not enabled (no .jcheck in repository root); skipping\n")
 597         return Pass
 598     strict = opts.has_key("strict") and opts["strict"]
 599     lax = opts.has_key("lax") and opts["lax"]
 600     if strict:
 601         lax = False
 602     ch = checker(ui, repo, strict, lax)
 603     ch.check_repo()
 604     firstnode = bin(node)
 605     start = repo.changelog.rev(firstnode)
 606     end = (hasattr(repo.changelog, 'count') and repo.changelog.count() or
 607            len(repo.changelog))
 608     for rev in xrange(start, end):
 609         ch.check(repo.changelog.node(rev))
 610     if ch.rv == Fail:
 611         ui.status("\n")
 612     return ch.rv
 613 
 614 
 615 # Run this hook in repository gates
 616 
 617 def strict_hook(ui, repo, hooktype, node=None, source=None, **opts):
 618     opts["strict"] = True
 619     return hook(ui, repo, hooktype, node, source, **opts)
 620 
 621 
 622 def jcheck(ui, repo, **opts):
 623     """check changesets against JDK standards"""
 624     ui.debug("jcheck repo=%s opts=%s\n" % (repo.path, opts))
 625     repocompat(repo)
 626     if not repo.local():
 627         raise util.Abort("repository '%s' is not local" % repo.path)
 628     if not os.path.exists(os.path.join(repo.root, ".jcheck")):
 629         ui.status("jcheck not enabled (no .jcheck in repository root)\n")
 630         return Pass
 631     if len(opts["rev"]) == 0:
 632         opts["rev"] = ["tip"]
 633 
 634     strict = opts.has_key("strict") and opts["strict"]
 635     lax = opts.has_key("lax") and opts["lax"]
 636     if strict:
 637         lax = False
 638     ch = checker(ui, repo, strict, lax)
 639     ch.check_repo()
 640 
 641     try:
 642         nop = lambda c, fns: None
 643         iter = cmdutil.walkchangerevs(repo, _matchall(repo), opts, nop)
 644         for ctx in iter:
 645             ch.check(ctx.node())
 646     except (AttributeError, TypeError):
 647         # AttributeError:  matchall does not exist in hg < 1.1
 648         # TypeError:  walkchangerevs args differ in hg <= 1.3.1
 649         get = util.cachefunc(lambda r: repo.changectx(r).changeset())
 650         changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, opts)
 651         if ui.debugflag:
 652             displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
 653         for st, rev, fns in changeiter:
 654             if st == 'add':
 655                 node = repo.changelog.node(rev)
 656                 if ui.debugflag:
 657                     displayer.show(rev, node, copies=False)
 658                 ch.check(node)
 659             elif st == 'iter':
 660                 if ui.debugflag:
 661                     displayer.flush(rev)
 662 
 663     if ch.rv == Fail:
 664         ui.status("\n")
 665     return ch.rv
 666 
 667 # This is invoked on servers to check pushkeys; it's not needed on clients.
 668 def prepushkey(ui, repo, hooktype, namespace, key, old=None, new=None, **opts):
 669     if namespace == 'phases':
 670         return Pass
 671     ui.write_err('ERROR:  pushing keys (%s) is disabled\n' % namespace)
 672     return Fail
 673 
 674 opts = [("", "lax", False, "Check comments, tags and whitespace laxly"),
 675         ("r", "rev", [], "check the specified revision or range (default: tip)"),
 676         ("s", "strict", False, "check everything")]
 677 
 678 help = "[-r rev] [-s]"
 679 
 680 cmdtable = {
 681     "jcheck": (jcheck, opts, "hg jcheck " + help)
 682 }