1 #
   2 # Copyright (c) 2007, 2017, 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-([1-9]([0-9]*)(\.[0-9]){0,3})\+([0-9]+)$|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     return bugids
 248 
 249 
 250 
 251 # Black/white lists
 252 ## The black/white lists should really be in the database
 253 
 254 # Bogus yet historically-accepted changesets,
 255 # so that jcheck may evolve
 256 #
 257 changeset_whitelist = [
 258 
 259     '31000d79ec713de1e601dc16d74d726edd661ed5',
 260     'b7987d19f5122a9f169e568f935b7cdf1a2609f5',
 261     'c70a245cad3ad74602aa26b9d8e3d0472f7317c3',
 262     'e8e20316458c1cdb85d9733a2e357e438a76a859',
 263     'f68325221ce1efe94ab367400a49a8039d9b3db3',
 264     '4dfa5d67c44500155ce9ab1e00d0de21bdbb9ee6',
 265     '73a4d5be86497baf74c1fc194c9a0dd4e86d3a31', # jdk6/jdk6/jaxp bad comment
 266     'a25f15bfd04b46a302b6ca1a298c176344f432dd', # jdk6/jdk6/jdk  bad comment
 267     'bf87d5af43614d609a5251c43eea44c028500d02', # jdk6/jdk6/jdk  bad comment
 268     'd77434402021cebc4c25b452db18bbfd2d7ccda1', # jdk6/jdk6/jdk  bad comment
 269     '931e5f39e365a0d550d79148ff87a7f9e864d2e1', # hotspot dup bugid 7147064
 270     'd8abc90163a4b58db407a60cba331ab21c9977e7', # hotspot dup bugid 7147064
 271     '45849c62c298aa8426c9e67599e4e35793d8db13', # pubs executable files
 272     '38050e6655d8acc220800a28128cef328906e825', # pubs invalid bugid line
 273     # hotspot/test/closed no Reviewed-by line
 274     '8407fef5685f32ed42b79c5d5f13f6c8007171ac',
 275     'c667bae72ea8530ef1e055dc25951b991dfd5888', # hotspot dup bugid 8169597 (hs)
 276     '5a574ef5a4eec3ec3be9352aae3b383202c9a3a6', # hotspot dup bugid 8169597 (dev)
 277     '38a240fd58a287acb1963920b92ed4d9c2fd39e3', # hotspot dup bugid 8179954 (jdk10)
 278     'fc8c54b03f821dbc7385ab6b08cb91cc7a3bf3cb', # hotspot dup bugid 8179954 (hs)
 279     # For duplicate bugids, add the hashes of all related changesets!
 280 
 281     # consolidated/open
 282     '489c9b5090e2bdfe3a2f196afe013025e7443f6b',
 283     '90ce3da70b431eab8f123abd25ceda9e53a094a9',
 284     '02bb8761fcce2922d1619062a303dbce266068a9',
 285     '01d07c8452ff8e96f3ff777f0b50e1c98774b9df',
 286     '7f561c08de6b09951cf79975dba08150982c7bb3',
 287     'af5240695a6de2c89f01e6de58e9bad6f582c9ff',
 288     '474761f14bcad3a18b5e6990447402c3a24d5fea',
 289     'aa192ed8538b76aa647e9cdd89e485b5f10e0a26',
 290     '06bc494ca11ef44070b1ea054c34c3655c93ddb2',
 291     '1cc8dd79fd1cd13d36b385196271a29632c67c3b',
 292     '408b55da75b0ae21ce9f6f27a798d051d4675e4e',
 293     '74fe6922716dbd4e10b5d5efbf23af551b84a697',
 294     '51a7bc3e93a011c733f83ab871612ccdc6216a75',
 295     '5b0720709093938bc2e0d6e4522059d893462738',
 296     '05173e59b8785ba423d0a582d06957596dce193d',
 297     '80e13954d5b063a2275778e96e639b4282861610',
 298     '4e9d88727ae307a6430931dad8e569bc0bf465c4',
 299     'e14008f86acd1d3860aa4cce7d5fe62c70529d48',
 300     'abae672ab077e142e228067a63490868da536c60',
 301     '898f519b613889dbce3c16c2baf482d1f4243f8e',
 302     '7e19cecfbfc8bf88b52fc88758817f780bf188a1',
 303     '3b2c9223cdf512ba11c7db61f196a187d82d0036',
 304     '370f960bd6dbf4cd670625eab08364a190f9afc3',
 305     '564d8dc66b61329bbe2576a93b68d41d3ccdba00',
 306     '249e283e044665a83dbce8e75a97bf63f83cb102',
 307     '3d179532574942423bcb9fbdf4c7afe003ccceeb',
 308     '71e33d83609b052fc9490b1822829ca692662d71',
 309     '862a85ed20dbdf0efc1539cc83aff7dff60194ef',
 310     '14672d061f7a42801f3feab49228f36272ded78a',
 311     '0d803d2ebd6b4544145331fb7f2e4c0eb0f0ad64',
 312     '59439733e87a34e9b41bd003c3ab7580112fc4f3',
 313     'e12adc424b4309163099c518e771c7eb159f94a4',
 314     '11c76613f3a2143d253fb3c389119f41188d709d',
 315     'bbe9212c700515be4c8c5dff0354104386810e8c',
 316     'f0e156a39c75441522f05bc7abc2675a37ea0b1c',
 317     'd1f02d5e4c740acc0b2b5651126f38090a556171',
 318     '7b3eaf04308f28aac3d21e05e8487df9d29719a4',
 319     '011727a60840e202a9c556d840636e3907fd0ce1',
 320     '425e2c6b5941e31797c6feca184ecfbd7c1c077b',
 321     '0f8aea9a422ed9a888623e0f918cfc71be8a5a24',
 322     'a8ab83cbaa49a9232ed8153d731bc9e328f6ee61',
 323 
 324     # consolidated/closed
 325     'e7e6bffe1f8028ba4daf575385dc4fd578034d2f',
 326     '2523cc85defa8d570b0b211c38f2b08fc457eb6c',
 327     '47c62354c6da8cd5022d92babafc269878a9340f',
 328     '01a573cdd8d0a26a851dffdf126f96fbd829ac6e',
 329     '26373189d6f8f9f6eed4d9a6ac2345cc75388a80',
 330     'ca94fe25e6a503e9641c482b5d76c4d55b0ac297',
 331     'a89ff61916e503648783883124f60a459e25df1f',
 332     'f41443d20e3bdca9a16b94a7a464cb7ac9b2ca73',
 333     '0e2c107c7319e4bbdc8ee80c4dba3d87329ee19f',
 334     '06905d1339554298cecfa9d599e6fbaefbcd8df7',
 335     '324534d86a9cad44404dcfcff5e45a21d91eb445',
 336     'd4c8044fa522ae5e89215324f7035e0ec9f8df55',
 337     '76ec26e0c56712624e6a5809929571a5bd028576',
 338     '38557f4c06fdc2209ede8044dd7bd6893ea365f4',
 339     '015b1a27f5352eb24ad975b1a9f45a1d62d4e977',
 340     'dfae63c29a6cc3254097065c629d85dac5d10c81',
 341     '87a0ce109f0f1de36e4520cfd020926b2b4a2cbc',
 342     '5bc60aea1e1634843c79f5426d8f682a37e2092f',
 343     '199381c054109f57ffcd2291fa343c528b53b6d9',
 344     '22f717ecdcce500190b685763bcddc68d55d3316',
 345     'ece95c3640926c371c885358ab6b54e18579e3e2',
 346     '2c88ed83131005533c9a43d5da1f5fd7ff5675d8',
 347     '38835cfd0829bd91cfbe5a94ff761c92004cdd07',
 348     '3782924e5ad1e331fa221a4f37d2cabe9b3734fb',
 349     '70ff4a44bcf94be3b4bdfb9189e70e2d08aaf8c0',
 350     'd999bdc2f2bea8761b6b430115e84c18d4fcf6a4',
 351     '2d8b9e27c05ed338badf0bb82b1f22fa13c0a2d2',
 352     '667bbf13b1bf6b50074fa80240cea77c2c0b21ba',
 353     '94deb45ef34f9dab46d8401d51ce446d072f4917',
 354     '58e382f36a016ed31b71544256139fdd10a405c3',
 355     'd5b7c3f4f5220ae0e927501ae53e43652668b5ae',
 356     '443167e10bc4eed20301adef5121af9394e844e3',
 357     '8b1f7ef1cd682b16d5f41b34c5d474adf2cf11ab',
 358     '9fd855244664fa1ba553bc823a6e8fed1183ad32',
 359     '5e64c143b0c6163ac815ea159fa8c11b57ccc445',
 360     '6a8e2a080676822e67f9b0d51943c8110ec861b0',
 361     '4ce78429449f8100ccb289b51d63b055cec37223',
 362     'e46a0e002a57095855bb632edb447597cf6cecf7',
 363     '7cb53066e470c26002191263a664350034d49bff',
 364     '840eac30564f5304dbaaec276a2dabf353c7f623',
 365     'fd67174f8a7708238c84896603a960ea9b5e3cca',
 366 
 367     ]
 368 
 369 # Bad changesets that should never be allowed in
 370 #
 371 changeset_blacklist = [
 372     'd3c74bae36884525be835ea428293bb6e7fa54d3',
 373     '2bb5ef5c8a2dc0a32b1cd67803128ce12cad461e',
 374     '4ff95cec682e67a374f9c5725d1879f43624d888',
 375     '75f1884152db275047a09aa6085ae7c49e3f4126',
 376     '6ecad8bfb1e5d34aef2ca61d30c1d197745d6844',
 377     '4ec7d1890538c54a3cc7559e88db5a5e3371fe5d',
 378     '669768c591ac438f4ca26d5cbdde7486ce49a2e2',
 379     '2ded3bb1452943d5273e7b83af9609ce6511a105',
 380     # hsdev/hotspot/{hotspot,master} dup bugid 7019157
 381     '0d8777617a2d028ba0b82943c829a5c6623f1479',
 382     # hsx/hotspot-comp/jdk dup bugid 7052202 + follow-on cset
 383     'ad2d483067099421f3ea4492269cce69009b046f',
 384     '521e2254994c76441c25f4374e16abbe314d8143',
 385     # hsx/hotspot-rt/hotspot wrong bugid 7059288 + associated merge
 386     'aa5f3f5978991182b8dbbd2b46fdcb47b6371dd9',
 387     '709d9389b2bc290dad5a35ec5b5f951b07ce9631',
 388     # jdk8/awt/jdk dup bugid 7100054
 389     'f218e6bdf1e8e20ca3f0fdaeb29c38f56afdf988',
 390     # jdk7u/jdk7u-dev/jaxp mistaken push
 391     '24f4c1185305b60818d255550a0fdc1ddf52c2a6',
 392     # jdk8/build/pubs executable file
 393     '2528f2a1117000eb98891a139e8f839fc5e2bfab',
 394     # jdk8/2d/jdk/src/closed security fix in wrong forest
 395     '8c7fbf082af5ec6d7ad0b1789cedc98a597f1f83',
 396     # jdk7u/jdk7u5/jdk bad fix for 6648202
 397     'b06f6d9a6c329792401b954682d49169484b586d',
 398     # hsx/hsx24/hotspot/src/closed bad merge
 399     '306614eb47a79e6d25a8c7447d0fe47fac28b24c',
 400     # hsx/hsx24/hotspot/test/closed bad merge
 401     '96163ee390bf223fe0037739fc953e8ed7d49560',
 402     # jdk8/awt/jdk INTJDK-7600365
 403     '6be9b0bda6dccbfc738b9173a71a15dcafda4f3b',
 404     # jdk8/tl/jdk/test/closed INTJDK-7600460
 405     '9eb97a2b3274edff83a362f76bbadb866a97a89b',
 406     # jdk7u11-dev/jaxp bad fix for 7192390
 407     '1642814c94fd0206f8b4f460cc77fa6fc099731a',
 408     # jdk7u11-dev/jdk bad fix for 7192390
 409     '90eb0407ca69dc572d20490f17239b183bb616b6',
 410     # jdk7u11-dev/jdk/test/closed bad fix for 7192390
 411     '512af24c6909ef2c766c3a5217c719545de68bf7',
 412     # jdk7u11-dev/jdk redone rmi fix
 413     'fd6ce0200a7f519380e6863930e92f9182030fa0',
 414     # jdk7u11-dev/jdk/test/closed redone rmi fix
 415     '770d9cf0e1dc97f7aaa3fdfbb430b27a40b1a3a9',
 416     # jdk7u13-dev/jdk bad fix for 8006611
 417     '12b6a43f9fefa6c3bbf81d9096e764e72bacf065',
 418     # jdk8/nashorn unwanted tag jdk8-b78
 419     '8f49d8121c7e63d22f55412df4ff4800599686d6',
 420     # hsx/hotspot-emb/hotspot wrong bugid 8009004
 421     '29ab68ef5bb643f96218126dc2ff845561d552a4',
 422     # jdk7u40/jdk/src/closed mistaken push 8016315
 423     'd2b0a0c38c808bddff604a025469c5102a62edfe',
 424     # jdk7u40/jdk/test/closed mistaken push 8016622
 425     'd1e0d129aa0fccdc1ff1bcf20f126ffea900f30b',
 426     # hsx/hotspot-gc/hotspot wrong bugid 8024547
 427     '9561e0a8a2d63c45e751755d791e25396d94025a',
 428     # jdk8/ds/install dup bugid 8024771
 429     '835ef04a36e5fe95d4c0deb6e633371053f3cbba',
 430     # jdk5u/jdk5.0u55/j2se bad fix 8025034
 431     'e18574aa4be397b83a43e1444cb96c903f152fcb',
 432     # jdk6u/jdk6u65/j2se bad fix 8025034
 433     'a0f1589decc6181a5e048e48058d12cfa68cd3e1',
 434     # jdk7u/jdk7u45/j2se bad fix 8025034
 435     '0a312c04dbc8d33601efecfb0d23b8c09bf088fe',
 436     # jdk8/build/pubs executable files
 437     '3ecd3336c805978a37a933fbeca26c65fbe81432',
 438     # hsx/jdk7u/hotspot wrong bugid
 439     'f5d8e6d72e23d972db522f7ad4cd3b9b01085466',
 440     # jdk8/tl/jdk erroneous push 7152892
 441     'da4b0962ad1161dbd84e7daa0fdc706281c456a2',
 442     # jdk8/tl/jdk/test/closed erroneous push 7152892
 443     '1e69a1ce212c7c4c884f155dd123c936787db273',
 444     # jdk9/jdk9/closed bad tag
 445     '61fdebb503d79392536b8f502ae215022d1a1f1c',
 446     # jdk9/hs-rt/jdk/src/closed dup bugid 8034951
 447     'a19596796430761dde87bee9f6616480f1c93678',
 448     # jdk9/hs-rt/jdk/test/closed dup bugid 8034951
 449     'd2308c9714c94e87a0e60cda314746a5c17dbcc2',
 450     # jdk9/client/deploy erroneous push 8041798
 451     'fff4ff4fd6f031ab335b44842d69fd125297b5ab',
 452     ]
 453 
 454 # Path to file containing additional blacklisted changesets
 455 blacklist_file = '/oj/db/hg/blacklist'
 456 
 457 
 458 # Checker class
 459 
 460 class checker(object):
 461 
 462     def __init__(self, ui, repo, strict, lax):
 463         self.ui = ui
 464         self.repo = repo
 465         self.rv = Pass
 466         self.checks = [c for c in checker.__dict__ if c.startswith("c_")]
 467         self.checks.sort()
 468         self.summarized = False
 469         self.repo_bugids = [ ]
 470         self.cs_bugids = [ ]            # Bugids in current changeset
 471         self.cs_author = None           # Author of current changeset
 472         self.cs_reviewers = [ ]         # Reviewers of current changeset
 473         self.cs_contributor = None      # Contributor of current changeset
 474         self.strict = strict
 475         self.conf = load_conf(repo.root)
 476         self.whitespace_lax = lax and not strict
 477         if self.conf.get("whitespace") == "lax":
 478             self.whitespace_lax = True
 479         self.comments_lax = lax and not strict
 480         if self.conf.get("comments") == "lax":
 481             self.comments_lax = True
 482         self.tags_lax = lax and not strict
 483         if self.conf.get("tags") == "lax":
 484             self.tags_lax = True
 485         self.bugids_allow_dups = self.conf.get("bugids") == "dup"
 486         self.bugids_lax = lax and not strict
 487         if self.conf.get("bugids") == "lax":
 488             self.bugids_lax = True
 489         self.bugids_ignore = False
 490         if self.conf.get("bugids") == "ignore":
 491             self.bugids_ignore = True
 492         if not self.bugids_ignore:
 493             # only identify bug ids if we are going to use them
 494             self.repo_bugids = repo_bugids(ui, repo)
 495         self.blacklist = dict.fromkeys(changeset_blacklist)
 496         self.read_blacklist(blacklist_file)
 497         # hg < 1.0 does not have localrepo.tagtype()
 498         self.tagtype = getattr(self.repo, 'tagtype', lambda k: 'global')
 499 
 500     def read_blacklist(self, fname):
 501         if not os.path.exists(fname):
 502             return
 503         self.ui.debug('Reading blacklist file %s\n' % fname)
 504         f = open(fname)
 505         for line in f:
 506             # Any comment after the changeset hash becomes the dictionary value.
 507             l = [s.strip() for s in line.split('#', 1)]
 508             if l and l[0]:
 509                 self.blacklist[l[0]] = len(l) == 2 and l[1] or None
 510         f.close()
 511 
 512     def summarize(self, ctx):
 513         self.ui.status("\n")
 514         self.ui.status("> Changeset: %d:%s\n" % (ctx.rev(), short(ctx.node())))
 515         self.ui.status("> Author:    %s\n" % ctx.user())
 516         self.ui.status("> Date:      %s\n" % datestr(ctx))
 517         self.ui.status(">\n> ")
 518         self.ui.status("\n> ".join(ctx.description().splitlines()))
 519         self.ui.status("\n\n")
 520 
 521     def error(self, ctx, msg):
 522         if self.rv != Fail:
 523             self.ui.status("[jcheck %s %s]\n" % (_version, _date))
 524         if not self.summarized:
 525             if ctx:
 526                 self.summarize(ctx)
 527             else:
 528                 self.ui.status("\n")
 529             self.summarized = True
 530         self.ui.status(msg + "\n")
 531         self.rv = Fail
 532 
 533     def c_00_author(self, ctx):
 534         self.ui.debug("author: %s\n" % ctx.user())
 535         if not validate_author(ctx.user(), self.conf["project"]):
 536             self.error(ctx, "Invalid changeset author: %s" % ctx.user())
 537         self.cs_author = ctx.user()
 538 
 539     def c_01_comment(self, ctx):
 540         m = badwhite_re.search(ctx.description())
 541         if m:
 542             ln = ctx.description().count("\n", 0, m.start()) + 1
 543             self.error(ctx, "%s in comment (line %d)" % (badwhite_what(m), ln))
 544 
 545         if is_merge(self.repo, ctx.rev()):
 546             if ctx.description() != "Merge":
 547                 self.error(ctx, ("Invalid comment for merge changeset"
 548                                  + " (must be \"Merge\")"))
 549             return
 550 
 551         if tag_desc_re.match(ctx.description()):
 552             ## Should check tag itself
 553             return
 554 
 555         if ((ctx.rev() == 0 or (ctx.rev() == 1 and self.comments_lax))
 556             and ctx.user() == "duke"
 557             and ctx.description().startswith("Initial load")):
 558             return
 559 
 560         lns = ctx.description().splitlines()
 561 
 562         # If lax, filter out non-matching lines
 563         if self.comments_lax:
 564             lns = filter(checked_comment_line, lns)
 565 
 566         i = 0                           # Input index
 567         gi = -1                         # Grammar index
 568         n = 0                           # Occurrence count
 569         while i < len(lns):
 570             gi = gi + 1
 571             if gi >= len(comment_grammar):
 572                 break
 573             ln = lns[i]
 574             st = comment_grammar[gi]
 575             n = 0
 576             while (st.ident_pattern.match(ln)):
 577                 m = st.check_pattern.match(ln)
 578                 if not m:
 579                     if not (st.name == "bugid line" and (self.bugids_lax or self.bugids_ignore)):
 580                         self.error(ctx, "Invalid %s" % st.name)
 581                 elif st.validator:
 582                     if not (st.name == "bugid line" and self.bugids_ignore):
 583                         st.validator(self, ctx, m, self.conf["project"])
 584                 n = n + 1
 585                 i = i + 1
 586                 if i >= len(lns):
 587                     break;
 588                 ln = lns[i]
 589             if n < st.min and not self.comments_lax:
 590                 self.error(ctx, "Incomplete comment: Missing %s" % st.name)
 591             if n > st.max:
 592                 self.error(ctx, "Too many %ss" % st.name)
 593 
 594         if not self.cs_contributor and [self.cs_author] == self.cs_reviewers:
 595             self.error(ctx, "Self-reviews not permitted")
 596         if not self.comments_lax:
 597             if (gi == 0 and n > 0):
 598                 self.error(ctx, "Incomplete comment: Missing bugid line")
 599             elif gi == 1 or (gi == 2 and n == 0):
 600                 self.error(ctx, "Incomplete comment: Missing reviewer attribution")
 601             if (i < len(lns)):
 602                 self.error(ctx, "Extraneous text in comment")
 603 
 604     def c_02_files(self, ctx):
 605         changes = self.repo.status(ctx.parents()[0].node(),
 606                                    ctx.node(), None)[:5]
 607         modified, added = changes[:2]
 608         ## Skip files that were renamed but not modified
 609         files = modified + added
 610         if self.ui.debugflag:
 611             self.ui.debug("Checking files: %s\n" % ", ".join(files))
 612         for f in files:
 613             if ctx.rev() == 0:
 614                 ## This is loathsome
 615                 if f.startswith("test/java/rmi"): continue
 616                 if f.startswith("test/com/sun/javadoc/test"): continue
 617                 if f.startswith("docs/technotes/guides"): continue
 618             fx = ctx.filectx(f)
 619             if normext_re.match(f) and not self.whitespace_lax:
 620                 data = fx.data()
 621                 if "\t" in data or "\r" in data or " \n" in data:
 622                     m = badwhite_re.search(data)
 623                     if m:
 624                         ln = data.count("\n", 0, m.start()) + 1
 625                         self.error(ctx, "%s:%d: %s" % (f, ln, badwhite_what(m)))
 626             ## check_file_header(self, fx, data)
 627             flags = fx.manifest().flags(f)
 628             if 'x' in flags:
 629                 self.error(ctx, "%s: Executable files not permitted" % f)
 630             if 'l' in flags:
 631                 self.error(ctx, "%s: Symbolic links not permitted" % f)
 632 
 633     def c_03_hash(self, ctx):
 634         hash = hex(ctx.node())
 635         if hash in self.blacklist:
 636             self.error(ctx, "Blacklisted changeset: " + hash)
 637 
 638     def check(self, node):
 639         self.summarized = False
 640         self.cs_bugids = [ ]
 641         self.cs_author = None
 642         self.cs_reviewers = [ ]
 643         self.cs_contributor = None
 644         ctx = context.changectx(self.repo, node)
 645         self.ui.note(oneline(ctx))
 646         if not self.strict and hex(node) in changeset_whitelist:
 647             self.ui.note("%s in whitelist; skipping\n" % hex(node))
 648             return Pass
 649         for c in self.checks:
 650             cf = checker.__dict__[c]
 651             cf(self, ctx)
 652         return self.rv
 653 
 654     def check_repo(self):
 655 
 656         if not self.tags_lax:
 657             ts = self.repo.tags().keys()
 658             ignoredtypes = ['local']
 659             for t in ts:
 660                 if not tag_re.match(t) and not self.tagtype(t) in ignoredtypes:
 661                     self.error(None,
 662                                "Illegal tag name: %s" % t)
 663 
 664         bs = self.repo.branchmap()
 665         if len(bs) > 1:
 666             bs = bs.copy()
 667             del bs["default"]
 668             self.error(None,
 669                        "Named branches not permitted; this repository has: %s"
 670                        % ", ".join(bs.keys()))
 671 
 672         if self.strict:
 673             nh = len(self.repo.heads())
 674             if nh > 1:
 675                 self.error(None,
 676                            "Multiple heads not permitted; this repository has %d"
 677                            % nh)
 678 
 679         return self.rv
 680 
 681 
 682 def hook(ui, repo, hooktype, node=None, source=None, **opts):
 683     ui.debug("jcheck: node %s, source %s, args %s\n" % (node, source, opts))
 684     repocompat(repo)
 685     if not repo.local():
 686         raise util.Abort("repository '%s' is not local" % repo.path)
 687     if not os.path.exists(os.path.join(repo.root, ".jcheck")):
 688         ui.note("jcheck not enabled (no .jcheck in repository root); skipping\n")
 689         return Pass
 690     strict = opts.has_key("strict") and opts["strict"]
 691     lax = opts.has_key("lax") and opts["lax"]
 692     if strict:
 693         lax = False
 694     ch = checker(ui, repo, strict, lax)
 695     ch.check_repo()
 696     firstnode = bin(node)
 697     start = repo.changelog.rev(firstnode)
 698     end = (hasattr(repo.changelog, 'count') and repo.changelog.count() or
 699            len(repo.changelog))
 700     for rev in xrange(start, end):
 701         ch.check(repo.changelog.node(rev))
 702     if ch.rv == Fail:
 703         ui.status("\n")
 704     return ch.rv
 705 
 706 
 707 # Run this hook in repository gates
 708 
 709 def strict_hook(ui, repo, hooktype, node=None, source=None, **opts):
 710     opts["strict"] = True
 711     return hook(ui, repo, hooktype, node, source, **opts)
 712 
 713 # From Mercurial 1.9, the preferred way to define commands is using the @command
 714 # decorator. If this isn't available, fallback on a simple local implementation
 715 # that just adds the data to the cmdtable.
 716 cmdtable = {}
 717 if hasattr(cmdutil, 'command'):
 718     command = cmdutil.command(cmdtable)
 719 else:
 720     def command(name, options, synopsis):
 721         def decorator(func):
 722             cmdtable[name] = func, list(options), synopsis
 723             return func
 724         return decorator
 725 
 726 opts = [("", "lax", False, "Check comments, tags and whitespace laxly"),
 727         ("r", "rev", [], "check the specified revision or range (default: tip)"),
 728         ("s", "strict", False, "check everything")]
 729 
 730 help = "[-r rev] [-s]"
 731 
 732 @command("jcheck", opts, "hg jcheck " + help)
 733 def jcheck(ui, repo, **opts):
 734     """check changesets against JDK standards"""
 735     ui.debug("jcheck repo=%s opts=%s\n" % (repo.path, opts))
 736     repocompat(repo)
 737     if not repo.local():
 738         raise util.Abort("repository '%s' is not local" % repo.path)
 739     if not os.path.exists(os.path.join(repo.root, ".jcheck")):
 740         ui.status("jcheck not enabled (no .jcheck in repository root)\n")
 741         return Pass
 742     if len(opts["rev"]) == 0:
 743         opts["rev"] = ["tip"]
 744 
 745     strict = opts.has_key("strict") and opts["strict"]
 746     lax = opts.has_key("lax") and opts["lax"]
 747     if strict:
 748         lax = False
 749     ch = checker(ui, repo, strict, lax)
 750     ch.check_repo()
 751 
 752     try:
 753         nop = lambda c, fns: None
 754         iter = cmdutil.walkchangerevs(repo, _matchall(repo), opts, nop)
 755         for ctx in iter:
 756             ch.check(ctx.node())
 757     except (AttributeError, TypeError):
 758         # AttributeError:  matchall does not exist in hg < 1.1
 759         # TypeError:  walkchangerevs args differ in hg <= 1.3.1
 760         get = util.cachefunc(lambda r: repo.changectx(r).changeset())
 761         changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, opts)
 762         if ui.debugflag:
 763             displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
 764         for st, rev, fns in changeiter:
 765             if st == 'add':
 766                 node = repo.changelog.node(rev)
 767                 if ui.debugflag:
 768                     displayer.show(rev, node, copies=False)
 769                 ch.check(node)
 770             elif st == 'iter':
 771                 if ui.debugflag:
 772                     displayer.flush(rev)
 773 
 774     if ch.rv == Fail:
 775         ui.status("\n")
 776     return ch.rv
 777 
 778 # This is invoked on servers to check pushkeys; it's not needed on clients.
 779 def prepushkey(ui, repo, hooktype, namespace, key, old=None, new=None, **opts):
 780     if namespace == 'phases':
 781         return Pass
 782     ui.write_err('ERROR:  pushing keys (%s) is disabled\n' % namespace)
 783     return Fail