1 #
   2 # Copyright (c) 2007, 2018, 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, json
  47 from mercurial.node import *
  48 from mercurial import cmdutil, context, error, patch, templater, util, utils
  49 try:
  50     # Mercurial 4.3 and higher
  51     from mercurial import registrar
  52 except ImportError:
  53     registrar = {}
  54     pass
  55 
  56 # Abort() was moved/copied from util to error in hg 1.3 and was removed from
  57 # util in 4.6.
  58 error_Abort = None
  59 if hasattr(error, 'Abort'):
  60     error_Abort = error.Abort
  61 else:
  62     error_Abort = util.Abort
  63 
  64 # date-related utils moved to utils/dateutil in early 2018 (hg 4.7)
  65 dateutil_datestr = None
  66 if hasattr(utils, 'dateutil'):
  67     dateutil_datestr = utils.dateutil.datestr
  68 else:
  69     dateutil_datestr = util.datestr
  70 
  71 Pass = False
  72 Fail = True
  73 
  74 def datestr(ctx):
  75     # Mercurial 0.9.5 and earlier append a time zone; strip it.
  76     return dateutil_datestr(ctx.date(), format="%Y-%m-%d %H:%M")[:16]
  77 
  78 def oneline(ctx):
  79     return ("%5d:%s  %-12s  %s  %s\n"
  80             % (ctx.rev(), short(ctx.node()), ctx.user(), datestr(ctx),
  81                ctx.description().splitlines()[0]))
  82 
  83 def is_merge(repo, rev):
  84     return not (-1 in repo.changelog.parentrevs(rev))
  85 
  86 _matchall = getattr(cmdutil, 'matchall', None)
  87 if not _matchall:
  88     try:
  89         from mercurial import scmutil
  90         _matchall = scmutil.matchall
  91     except ImportError:
  92         pass
  93 
  94 def repocompat(repo):
  95     # Modern mercurial versions use len(repo) and repo[cset_id]; enable those
  96     # operations with older versions.
  97     t = type(repo)
  98     if not getattr(t, '__len__', None):
  99         def repolen(self):
 100             return self.changelog.count()
 101         setattr(t, '__len__', repolen)
 102     if not getattr(t, '__getitem__', None):
 103         def repoitem(self, arg):
 104             return context.changectx(self, arg)
 105         setattr(t, '__getitem__', repoitem)
 106     # Similarly, use branchmap instead of branchtags; enable it if needed.
 107     if not getattr(t, 'branchmap', None):
 108         setattr(t, 'branchmap', t.branchtags)
 109 
 110 
 111 # Configuration-file parsing
 112 
 113 def load_conf(root):
 114     cf = { }
 115     fn = os.path.join(root, ".jcheck/conf")
 116     f = open(fn)
 117     try:
 118         prop_re = re.compile("\s*(\S+)\s*=\s*(\S+)\s*$")
 119         i = 0
 120         for ln in f.readlines():
 121             i = i + 1
 122             ln = ln.strip()
 123             if (ln.startswith("#")):
 124                 continue
 125             m = prop_re.match(ln)
 126             if not m:
 127                 raise error_Abort("%s:%d: Invalid configuration syntax: %s"
 128                                  % (fn, i, ln))
 129             cf[m.group(1)] = m.group(2)
 130     finally:
 131         f.close()
 132     for pn in ["project"]:
 133         if not cf.has_key(pn):
 134             raise error_Abort("%s: Missing property: %s" % (fn, pn))
 135     return cf
 136 
 137 
 138 # Author validation
 139 
 140 author_cache = None
 141 
 142 def load_authors(ui):
 143     global author_cache
 144     ui.debug("Loading author names ...\n")
 145     u = "https://db.openjdk.java.net/people"
 146     author_cache = { }
 147     f = None
 148     try:
 149         req = urllib2.Request(u)
 150         req.add_header("Accept", "application/json")
 151         f = urllib2.urlopen(req)
 152         j = json.load(f)
 153         for p in j:
 154             author_cache[p['name']] = True
 155     finally:
 156         if f:
 157             f.close()
 158 
 159 def validate_author(ui, an, pn):
 160     if not author_cache:
 161         load_authors(ui)
 162     return author_cache.has_key(an)
 163 
 164 
 165 # Whitespace and comment validation
 166 
 167 badwhite_re = re.compile("(\t)|([ \t]$)|\r", re.MULTILINE)
 168 normext_re = re.compile(".*\.(java|c|h|cpp|hpp)$")
 169 
 170 tag_desc_re = re.compile("Added tag [^ ]+ for changeset [0-9a-f]{12}")
 171 tag_re = re.compile("tip$|jdk-([1-9]([0-9]*)(\.(0|[1-9][0-9]*)){0,4})(\+(([0-9]+))|(-ga))$|jdk[4-9](u\d{1,3})?-((b\d{2,3})|(ga))$|hs\d\d(\.\d{1,2})?-b\d\d$")
 172 
 173 def badwhite_what(m):
 174     if m.group(1):
 175         return "Tab character"
 176     if m.group(2):
 177         return "Trailing whitespace"
 178     return "Carriage return (^M)"
 179 
 180 base_addr_pat = "[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}"
 181 addr_pat = ("(" + base_addr_pat + ")"
 182             + "|(([-_a-zA-Z0-9][-_ a-zA-Z0-9]+) +<" + base_addr_pat + ">)")
 183 
 184 bug_ident = re.compile("(([A-Z][A-Z0-9]+-)?[0-9]+):")
 185 bug_check = re.compile("([0-9]{7}): \S.*$")
 186 sum_ident = re.compile("Summary:")
 187 sum_check = re.compile("Summary: \S.*")
 188 rev_ident = re.compile("Reviewed-by:")
 189 rev_check = re.compile("Reviewed-by: (([a-z0-9]+)(, [a-z0-9]+)*$)")
 190 con_ident = re.compile("Contributed-by:")
 191 con_check = re.compile("Contributed-by: ((" + addr_pat + ")(, (" + addr_pat + "))*)$")
 192 
 193 def bug_validate(ch, ctx, m, pn):
 194     bs = m.group(1)
 195     if not (bs[0] in ['1','2','4','5','6','7','8']):
 196         ch.error(ctx, "Invalid bugid: %s" % bs)
 197     b = int(bs)
 198     if b in ch.cs_bugids:
 199         ch.error(ctx, "Bugid %d used more than once in this changeset" % b)
 200     ch.cs_bugids.append(b)
 201     if not ch.bugids_allow_dups and b in ch.repo_bugids:
 202         r = ch.repo_bugids[b]
 203         if r < ctx.rev():
 204             ch.error(ctx, ("Bugid %d already used in this repository, in revision %d "
 205                            % (b, r)))
 206 
 207 def rev_validate(ch, ctx, m, pn):
 208     ans = re.split(", *", m.group(1))
 209     for an in ans:
 210         if not validate_author(ch.ui, an, pn) or an == "duke":
 211             ch.error(ctx, "Invalid reviewer name: %s" % an)
 212         ch.cs_reviewers.append(an)
 213 
 214 def con_validate(ch, ctx, m, pn):
 215     ch.cs_contributor = m.group(1)
 216 
 217 class State:
 218     def __init__(self, name, ident_pattern, check_pattern,
 219                  validator=None, min=0, max=1):
 220         self.name = name
 221         self.ident_pattern = ident_pattern
 222         self.check_pattern = check_pattern
 223         self.validator = validator
 224         self.min = min
 225         self.max = max
 226 
 227 comment_grammar = [
 228     State("bugid line",
 229           bug_ident, bug_check, validator=bug_validate, min=1, max=1000),
 230     State("change summary",
 231           sum_ident, sum_check, min=0, max=1),
 232     State("reviewer attribution",
 233           rev_ident, rev_check, validator=rev_validate, min=1, max=1),
 234     State("contributor attribution",
 235           con_ident, con_check, validator=con_validate, min=0, max=1)
 236 ]
 237 
 238 def checked_comment_line(ln):
 239     for st in comment_grammar:
 240         if st.ident_pattern.match(ln):
 241             return True
 242     return False
 243 
 244 def repo_bugids(ui, repo):
 245     def addbugids(bugids, ctx):
 246         lns = ctx.description().splitlines()
 247         for ln in lns:
 248             m = bug_check.match(ln)
 249             if m:
 250                 b = int(m.group(1))
 251                 if not b in bugids:
 252                     bugids[b] = ctx.rev()
 253 
 254     # Should cache this, eventually
 255     bugids = { }                        # bugid -> rev
 256     opts = { 'rev' : ['0:tip'] }
 257     ui.debug("Gathering bugids ...\n")
 258     try:
 259         nop = lambda c, fns: None
 260         iter = cmdutil.walkchangerevs(repo, _matchall(repo), opts, nop)
 261         for ctx in iter:
 262             addbugids(bugids, ctx)
 263     except (AttributeError, TypeError):
 264         # AttributeError:  matchall does not exist in hg < 1.1
 265         # TypeError:  walkchangerevs args differ in hg <= 1.3.1
 266         get = util.cachefunc(lambda r: repo.changectx(r).changeset())
 267         changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, opts)
 268         for st, rev, fns in changeiter:
 269             if st == 'add':
 270                 node = repo.changelog.node(rev)
 271                 addbugids(bugids, context.changectx(repo, node))
 272     return bugids
 273 
 274 
 275 
 276 # Black/white lists
 277 ## The black/white lists should really be in the database
 278 
 279 # Bogus yet historically-accepted changesets,
 280 # so that jcheck may evolve
 281 #
 282 changeset_whitelist = [
 283 
 284     '31000d79ec713de1e601dc16d74d726edd661ed5',
 285     'b7987d19f5122a9f169e568f935b7cdf1a2609f5',
 286     'c70a245cad3ad74602aa26b9d8e3d0472f7317c3',
 287     'e8e20316458c1cdb85d9733a2e357e438a76a859',
 288     'f68325221ce1efe94ab367400a49a8039d9b3db3',
 289     '4dfa5d67c44500155ce9ab1e00d0de21bdbb9ee6',
 290     '73a4d5be86497baf74c1fc194c9a0dd4e86d3a31', # jdk6/jdk6/jaxp bad comment
 291     'a25f15bfd04b46a302b6ca1a298c176344f432dd', # jdk6/jdk6/jdk  bad comment
 292     'bf87d5af43614d609a5251c43eea44c028500d02', # jdk6/jdk6/jdk  bad comment
 293     'd77434402021cebc4c25b452db18bbfd2d7ccda1', # jdk6/jdk6/jdk  bad comment
 294     '931e5f39e365a0d550d79148ff87a7f9e864d2e1', # hotspot dup bugid 7147064
 295     'd8abc90163a4b58db407a60cba331ab21c9977e7', # hotspot dup bugid 7147064
 296     '45849c62c298aa8426c9e67599e4e35793d8db13', # pubs executable files
 297     '38050e6655d8acc220800a28128cef328906e825', # pubs invalid bugid line
 298     # hotspot/test/closed no Reviewed-by line
 299     '8407fef5685f32ed42b79c5d5f13f6c8007171ac',
 300     'c667bae72ea8530ef1e055dc25951b991dfd5888', # hotspot dup bugid 8169597 (hs)
 301     '5a574ef5a4eec3ec3be9352aae3b383202c9a3a6', # hotspot dup bugid 8169597 (dev)
 302     '38a240fd58a287acb1963920b92ed4d9c2fd39e3', # hotspot dup bugid 8179954 (jdk10)
 303     'fc8c54b03f821dbc7385ab6b08cb91cc7a3bf3cb', # hotspot dup bugid 8179954 (hs)
 304     # For duplicate bugids, add the hashes of all related changesets!
 305 
 306     # consolidated/open
 307     '489c9b5090e2bdfe3a2f196afe013025e7443f6b',
 308     '90ce3da70b431eab8f123abd25ceda9e53a094a9',
 309     '02bb8761fcce2922d1619062a303dbce266068a9',
 310     '01d07c8452ff8e96f3ff777f0b50e1c98774b9df',
 311     '7f561c08de6b09951cf79975dba08150982c7bb3',
 312     'af5240695a6de2c89f01e6de58e9bad6f582c9ff',
 313     '474761f14bcad3a18b5e6990447402c3a24d5fea',
 314     'aa192ed8538b76aa647e9cdd89e485b5f10e0a26',
 315     '06bc494ca11ef44070b1ea054c34c3655c93ddb2',
 316     '1cc8dd79fd1cd13d36b385196271a29632c67c3b',
 317     '408b55da75b0ae21ce9f6f27a798d051d4675e4e',
 318     '74fe6922716dbd4e10b5d5efbf23af551b84a697',
 319     '51a7bc3e93a011c733f83ab871612ccdc6216a75',
 320     '5b0720709093938bc2e0d6e4522059d893462738',
 321     '05173e59b8785ba423d0a582d06957596dce193d',
 322     '80e13954d5b063a2275778e96e639b4282861610',
 323     '4e9d88727ae307a6430931dad8e569bc0bf465c4',
 324     'e14008f86acd1d3860aa4cce7d5fe62c70529d48',
 325     'abae672ab077e142e228067a63490868da536c60',
 326     '898f519b613889dbce3c16c2baf482d1f4243f8e',
 327     '7e19cecfbfc8bf88b52fc88758817f780bf188a1',
 328     '3b2c9223cdf512ba11c7db61f196a187d82d0036',
 329     '370f960bd6dbf4cd670625eab08364a190f9afc3',
 330     '564d8dc66b61329bbe2576a93b68d41d3ccdba00',
 331     '249e283e044665a83dbce8e75a97bf63f83cb102',
 332     '3d179532574942423bcb9fbdf4c7afe003ccceeb',
 333     '71e33d83609b052fc9490b1822829ca692662d71',
 334     '862a85ed20dbdf0efc1539cc83aff7dff60194ef',
 335     '14672d061f7a42801f3feab49228f36272ded78a',
 336     '0d803d2ebd6b4544145331fb7f2e4c0eb0f0ad64',
 337     '59439733e87a34e9b41bd003c3ab7580112fc4f3',
 338     'e12adc424b4309163099c518e771c7eb159f94a4',
 339     '11c76613f3a2143d253fb3c389119f41188d709d',
 340     'bbe9212c700515be4c8c5dff0354104386810e8c',
 341     'f0e156a39c75441522f05bc7abc2675a37ea0b1c',
 342     'd1f02d5e4c740acc0b2b5651126f38090a556171',
 343     '7b3eaf04308f28aac3d21e05e8487df9d29719a4',
 344     '011727a60840e202a9c556d840636e3907fd0ce1',
 345     '425e2c6b5941e31797c6feca184ecfbd7c1c077b',
 346     '0f8aea9a422ed9a888623e0f918cfc71be8a5a24',
 347     'a8ab83cbaa49a9232ed8153d731bc9e328f6ee61',
 348 
 349     # consolidated/closed
 350     'e7e6bffe1f8028ba4daf575385dc4fd578034d2f',
 351     '2523cc85defa8d570b0b211c38f2b08fc457eb6c',
 352     '47c62354c6da8cd5022d92babafc269878a9340f',
 353     '01a573cdd8d0a26a851dffdf126f96fbd829ac6e',
 354     '26373189d6f8f9f6eed4d9a6ac2345cc75388a80',
 355     'ca94fe25e6a503e9641c482b5d76c4d55b0ac297',
 356     'a89ff61916e503648783883124f60a459e25df1f',
 357     'f41443d20e3bdca9a16b94a7a464cb7ac9b2ca73',
 358     '0e2c107c7319e4bbdc8ee80c4dba3d87329ee19f',
 359     '06905d1339554298cecfa9d599e6fbaefbcd8df7',
 360     '324534d86a9cad44404dcfcff5e45a21d91eb445',
 361     'd4c8044fa522ae5e89215324f7035e0ec9f8df55',
 362     '76ec26e0c56712624e6a5809929571a5bd028576',
 363     '38557f4c06fdc2209ede8044dd7bd6893ea365f4',
 364     '015b1a27f5352eb24ad975b1a9f45a1d62d4e977',
 365     'dfae63c29a6cc3254097065c629d85dac5d10c81',
 366     '87a0ce109f0f1de36e4520cfd020926b2b4a2cbc',
 367     '5bc60aea1e1634843c79f5426d8f682a37e2092f',
 368     '199381c054109f57ffcd2291fa343c528b53b6d9',
 369     '22f717ecdcce500190b685763bcddc68d55d3316',
 370     'ece95c3640926c371c885358ab6b54e18579e3e2',
 371     '2c88ed83131005533c9a43d5da1f5fd7ff5675d8',
 372     '38835cfd0829bd91cfbe5a94ff761c92004cdd07',
 373     '3782924e5ad1e331fa221a4f37d2cabe9b3734fb',
 374     '70ff4a44bcf94be3b4bdfb9189e70e2d08aaf8c0',
 375     'd999bdc2f2bea8761b6b430115e84c18d4fcf6a4',
 376     '2d8b9e27c05ed338badf0bb82b1f22fa13c0a2d2',
 377     '667bbf13b1bf6b50074fa80240cea77c2c0b21ba',
 378     '94deb45ef34f9dab46d8401d51ce446d072f4917',
 379     '58e382f36a016ed31b71544256139fdd10a405c3',
 380     'd5b7c3f4f5220ae0e927501ae53e43652668b5ae',
 381     '443167e10bc4eed20301adef5121af9394e844e3',
 382     '8b1f7ef1cd682b16d5f41b34c5d474adf2cf11ab',
 383     '9fd855244664fa1ba553bc823a6e8fed1183ad32',
 384     '5e64c143b0c6163ac815ea159fa8c11b57ccc445',
 385     '6a8e2a080676822e67f9b0d51943c8110ec861b0',
 386     '4ce78429449f8100ccb289b51d63b055cec37223',
 387     'e46a0e002a57095855bb632edb447597cf6cecf7',
 388     '7cb53066e470c26002191263a664350034d49bff',
 389     '840eac30564f5304dbaaec276a2dabf353c7f623',
 390     'fd67174f8a7708238c84896603a960ea9b5e3cca',
 391 
 392     # Reviewed-by: duke
 393     '2ae445f57ac60fee1dcf4d518f605a71680261f9',
 394     '4814eec6a323bbe72113878a8adf86ce1206abb8',
 395     '17e70318af8bdcd89bc2d75829bc376061a9e24d',
 396     '413576d00672c3725dd7517703b96804d4fdea97',
 397     'd2a313368ccbaa1a3ce8276ead68461d08a7593e',
 398     'e70067b81b0b05cda3f84be73426ff97a681641b',
 399     '387a39577f09c87a06b502ccca9e608ea73ed4e4',
 400     '96179f26139e78f90b1cd6c528013d4c496f89be',
 401     '3683a58d8a6836175aa946afc026930b79a57204',
 402     '2d9dad1b821abffb3fd9e12a2d585f77bc0d5ff9',
 403     'e321560ac819c05274c59f46f5cc28ccfd4b38ec',
 404 
 405     ]
 406 
 407 # Bad changesets that should never be allowed in
 408 #
 409 changeset_blacklist = [
 410     'd3c74bae36884525be835ea428293bb6e7fa54d3',
 411     '2bb5ef5c8a2dc0a32b1cd67803128ce12cad461e',
 412     '4ff95cec682e67a374f9c5725d1879f43624d888',
 413     '75f1884152db275047a09aa6085ae7c49e3f4126',
 414     '6ecad8bfb1e5d34aef2ca61d30c1d197745d6844',
 415     '4ec7d1890538c54a3cc7559e88db5a5e3371fe5d',
 416     '669768c591ac438f4ca26d5cbdde7486ce49a2e2',
 417     '2ded3bb1452943d5273e7b83af9609ce6511a105',
 418     # hsdev/hotspot/{hotspot,master} dup bugid 7019157
 419     '0d8777617a2d028ba0b82943c829a5c6623f1479',
 420     # hsx/hotspot-comp/jdk dup bugid 7052202 + follow-on cset
 421     'ad2d483067099421f3ea4492269cce69009b046f',
 422     '521e2254994c76441c25f4374e16abbe314d8143',
 423     # hsx/hotspot-rt/hotspot wrong bugid 7059288 + associated merge
 424     'aa5f3f5978991182b8dbbd2b46fdcb47b6371dd9',
 425     '709d9389b2bc290dad5a35ec5b5f951b07ce9631',
 426     # jdk8/awt/jdk dup bugid 7100054
 427     'f218e6bdf1e8e20ca3f0fdaeb29c38f56afdf988',
 428     # jdk7u/jdk7u-dev/jaxp mistaken push
 429     '24f4c1185305b60818d255550a0fdc1ddf52c2a6',
 430     # jdk8/build/pubs executable file
 431     '2528f2a1117000eb98891a139e8f839fc5e2bfab',
 432     # jdk8/2d/jdk/src/closed security fix in wrong forest
 433     '8c7fbf082af5ec6d7ad0b1789cedc98a597f1f83',
 434     # jdk7u/jdk7u5/jdk bad fix for 6648202
 435     'b06f6d9a6c329792401b954682d49169484b586d',
 436     # hsx/hsx24/hotspot/src/closed bad merge
 437     '306614eb47a79e6d25a8c7447d0fe47fac28b24c',
 438     # hsx/hsx24/hotspot/test/closed bad merge
 439     '96163ee390bf223fe0037739fc953e8ed7d49560',
 440     # jdk8/awt/jdk INTJDK-7600365
 441     '6be9b0bda6dccbfc738b9173a71a15dcafda4f3b',
 442     # jdk8/tl/jdk/test/closed INTJDK-7600460
 443     '9eb97a2b3274edff83a362f76bbadb866a97a89b',
 444     # jdk7u11-dev/jaxp bad fix for 7192390
 445     '1642814c94fd0206f8b4f460cc77fa6fc099731a',
 446     # jdk7u11-dev/jdk bad fix for 7192390
 447     '90eb0407ca69dc572d20490f17239b183bb616b6',
 448     # jdk7u11-dev/jdk/test/closed bad fix for 7192390
 449     '512af24c6909ef2c766c3a5217c719545de68bf7',
 450     # jdk7u11-dev/jdk redone rmi fix
 451     'fd6ce0200a7f519380e6863930e92f9182030fa0',
 452     # jdk7u11-dev/jdk/test/closed redone rmi fix
 453     '770d9cf0e1dc97f7aaa3fdfbb430b27a40b1a3a9',
 454     # jdk7u13-dev/jdk bad fix for 8006611
 455     '12b6a43f9fefa6c3bbf81d9096e764e72bacf065',
 456     # jdk8/nashorn unwanted tag jdk8-b78
 457     '8f49d8121c7e63d22f55412df4ff4800599686d6',
 458     # hsx/hotspot-emb/hotspot wrong bugid 8009004
 459     '29ab68ef5bb643f96218126dc2ff845561d552a4',
 460     # jdk7u40/jdk/src/closed mistaken push 8016315
 461     'd2b0a0c38c808bddff604a025469c5102a62edfe',
 462     # jdk7u40/jdk/test/closed mistaken push 8016622
 463     'd1e0d129aa0fccdc1ff1bcf20f126ffea900f30b',
 464     # hsx/hotspot-gc/hotspot wrong bugid 8024547
 465     '9561e0a8a2d63c45e751755d791e25396d94025a',
 466     # jdk8/ds/install dup bugid 8024771
 467     '835ef04a36e5fe95d4c0deb6e633371053f3cbba',
 468     # jdk5u/jdk5.0u55/j2se bad fix 8025034
 469     'e18574aa4be397b83a43e1444cb96c903f152fcb',
 470     # jdk6u/jdk6u65/j2se bad fix 8025034
 471     'a0f1589decc6181a5e048e48058d12cfa68cd3e1',
 472     # jdk7u/jdk7u45/j2se bad fix 8025034
 473     '0a312c04dbc8d33601efecfb0d23b8c09bf088fe',
 474     # jdk8/build/pubs executable files
 475     '3ecd3336c805978a37a933fbeca26c65fbe81432',
 476     # hsx/jdk7u/hotspot wrong bugid
 477     'f5d8e6d72e23d972db522f7ad4cd3b9b01085466',
 478     # jdk8/tl/jdk erroneous push 7152892
 479     'da4b0962ad1161dbd84e7daa0fdc706281c456a2',
 480     # jdk8/tl/jdk/test/closed erroneous push 7152892
 481     '1e69a1ce212c7c4c884f155dd123c936787db273',
 482     # jdk9/jdk9/closed bad tag
 483     '61fdebb503d79392536b8f502ae215022d1a1f1c',
 484     # jdk9/hs-rt/jdk/src/closed dup bugid 8034951
 485     'a19596796430761dde87bee9f6616480f1c93678',
 486     # jdk9/hs-rt/jdk/test/closed dup bugid 8034951
 487     'd2308c9714c94e87a0e60cda314746a5c17dbcc2',
 488     # jdk9/client/deploy erroneous push 8041798
 489     'fff4ff4fd6f031ab335b44842d69fd125297b5ab',
 490     # jdk/jdk10 (closed) erroneous restoration of tests 8194908
 491     '050a07d47f72c341bb6cb47a85ea729791c9f350',
 492     # jdk-updates/jdk10u-cpu (closed) erroneous restoration of tests 8194916
 493     '92419449b854803e7b5f3f4b89dfcf6fd564aeef',
 494     # JDK 11 tag mistakenly pushed to closejdk/jdk
 495     '50c413e460dd4d7df4e3c14f5f4b705f0609957e',
 496     ]
 497 
 498 # Path to file containing additional blacklisted changesets
 499 blacklist_file = '/oj/db/hg/blacklist'
 500 
 501 
 502 # Checker class
 503 
 504 class checker(object):
 505 
 506     def __init__(self, ui, repo, strict, lax):
 507         self.ui = ui
 508         self.repo = repo
 509         self.rv = Pass
 510         self.checks = [c for c in checker.__dict__ if c.startswith("c_")]
 511         self.checks.sort()
 512         self.summarized = False
 513         self.repo_bugids = [ ]
 514         self.cs_bugids = [ ]            # Bugids in current changeset
 515         self.cs_author = None           # Author of current changeset
 516         self.cs_reviewers = [ ]         # Reviewers of current changeset
 517         self.cs_contributor = None      # Contributor of current changeset
 518         self.strict = strict
 519         self.conf = load_conf(repo.root)
 520         self.whitespace_lax = lax and not strict
 521         if self.conf.get("whitespace") == "lax":
 522             self.whitespace_lax = True
 523         self.comments_lax = lax and not strict
 524         if self.conf.get("comments") == "lax":
 525             self.comments_lax = True
 526         self.tags_lax = lax and not strict
 527         if self.conf.get("tags") == "lax":
 528             self.tags_lax = True
 529         self.bugids_allow_dups = self.conf.get("bugids") == "dup"
 530         self.bugids_lax = lax and not strict
 531         if self.conf.get("bugids") == "lax":
 532             self.bugids_lax = True
 533         self.bugids_ignore = False
 534         if self.conf.get("bugids") == "ignore":
 535             self.bugids_ignore = True
 536         if not self.bugids_ignore and not self.bugids_allow_dups:
 537             # only gather bug ids if we are going to use them
 538             self.repo_bugids = repo_bugids(ui, repo)
 539         self.blacklist = dict.fromkeys(changeset_blacklist)
 540         self.read_blacklist(blacklist_file)
 541         # hg < 1.0 does not have localrepo.tagtype()
 542         self.tagtype = getattr(self.repo, 'tagtype', lambda k: 'global')
 543 
 544     def read_blacklist(self, fname):
 545         if not os.path.exists(fname):
 546             return
 547         self.ui.debug('Reading blacklist file %s\n' % fname)
 548         f = open(fname)
 549         for line in f:
 550             # Any comment after the changeset hash becomes the dictionary value.
 551             l = [s.strip() for s in line.split('#', 1)]
 552             if l and l[0]:
 553                 self.blacklist[l[0]] = len(l) == 2 and l[1] or None
 554         f.close()
 555 
 556     def summarize(self, ctx):
 557         self.ui.status("\n")
 558         self.ui.status("> Changeset: %d:%s\n" % (ctx.rev(), short(ctx.node())))
 559         self.ui.status("> Author:    %s\n" % ctx.user())
 560         self.ui.status("> Date:      %s\n" % datestr(ctx))
 561         self.ui.status(">\n> ")
 562         self.ui.status("\n> ".join(ctx.description().splitlines()))
 563         self.ui.status("\n\n")
 564 
 565     def error(self, ctx, msg):
 566         if self.rv != Fail:
 567             self.ui.status("[jcheck %s %s]\n" % (_version, _date))
 568         if not self.summarized:
 569             if ctx:
 570                 self.summarize(ctx)
 571             else:
 572                 self.ui.status("\n")
 573             self.summarized = True
 574         self.ui.status(msg + "\n")
 575         self.rv = Fail
 576 
 577     def c_00_author(self, ctx):
 578         if not validate_author(self.ui, ctx.user(), self.conf["project"]):
 579             self.error(ctx, "Invalid changeset author: %s" % ctx.user())
 580         self.cs_author = ctx.user()
 581 
 582     def c_01_comment(self, ctx):
 583         m = badwhite_re.search(ctx.description())
 584         if m:
 585             ln = ctx.description().count("\n", 0, m.start()) + 1
 586             self.error(ctx, "%s in comment (line %d)" % (badwhite_what(m), ln))
 587 
 588         if is_merge(self.repo, ctx.rev()):
 589             if ctx.description() != "Merge":
 590                 self.error(ctx, ("Invalid comment for merge changeset"
 591                                  + " (must be \"Merge\")"))
 592             return
 593 
 594         if tag_desc_re.match(ctx.description()):
 595             ## Should check tag itself
 596             return
 597 
 598         if ((ctx.rev() == 0 or (ctx.rev() == 1 and self.comments_lax))
 599             and ctx.user() == "duke"
 600             and ctx.description().startswith("Initial load")):
 601             return
 602 
 603         lns = ctx.description().splitlines()
 604 
 605         # If lax, filter out non-matching lines
 606         if self.comments_lax:
 607             lns = filter(checked_comment_line, lns)
 608 
 609         i = 0                           # Input index
 610         gi = -1                         # Grammar index
 611         n = 0                           # Occurrence count
 612         while i < len(lns):
 613             gi = gi + 1
 614             if gi >= len(comment_grammar):
 615                 break
 616             ln = lns[i]
 617             st = comment_grammar[gi]
 618             n = 0
 619             while (st.ident_pattern.match(ln)):
 620                 m = st.check_pattern.match(ln)
 621                 if not m:
 622                     if not (st.name == "bugid line" and (self.bugids_lax or self.bugids_ignore)):
 623                         self.error(ctx, "Invalid %s" % st.name)
 624                 elif st.validator:
 625                     if not (st.name == "bugid line" and self.bugids_ignore):
 626                         st.validator(self, ctx, m, self.conf["project"])
 627                 n = n + 1
 628                 i = i + 1
 629                 if i >= len(lns):
 630                     break;
 631                 ln = lns[i]
 632             if n < st.min and not self.comments_lax:
 633                 self.error(ctx, "Incomplete comment: Missing %s" % st.name)
 634             if n > st.max:
 635                 self.error(ctx, "Too many %ss" % st.name)
 636 
 637         if not self.cs_contributor and [self.cs_author] == self.cs_reviewers:
 638             self.error(ctx, "Self-reviews not permitted")
 639         if not self.comments_lax:
 640             if (gi == 0 and n > 0):
 641                 self.error(ctx, "Incomplete comment: Missing bugid line")
 642             elif gi == 1 or (gi == 2 and n == 0):
 643                 self.error(ctx, "Incomplete comment: Missing reviewer attribution")
 644             if (i < len(lns)):
 645                 self.error(ctx, "Extraneous text in comment")
 646 
 647     def c_02_files(self, ctx):
 648         changes = self.repo.status(ctx.parents()[0].node(),
 649                                    ctx.node(), None)[:5]
 650         modified, added = changes[:2]
 651         # ## Skip files that were renamed but not modified
 652         files = modified + added
 653         if self.ui.debugflag:
 654             self.ui.debug("Checking files: %s\n" % ", ".join(files))
 655         for f in files:
 656             if ctx.rev() == 0:
 657                 ## This is loathsome
 658                 if f.startswith("test/java/rmi"): continue
 659                 if f.startswith("test/com/sun/javadoc/test"): continue
 660                 if f.startswith("docs/technotes/guides"): continue
 661             fx = ctx.filectx(f)
 662             if normext_re.match(f) and not self.whitespace_lax:
 663                 data = fx.data()
 664                 if "\t" in data or "\r" in data or " \n" in data:
 665                     m = badwhite_re.search(data)
 666                     if m:
 667                         ln = data.count("\n", 0, m.start()) + 1
 668                         self.error(ctx, "%s:%d: %s" % (f, ln, badwhite_what(m)))
 669             ## check_file_header(self, fx, data)
 670             flags = fx.manifest().flags(f)
 671             if 'x' in flags:
 672                 self.error(ctx, "%s: Executable files not permitted" % f)
 673             if 'l' in flags:
 674                 self.error(ctx, "%s: Symbolic links not permitted" % f)
 675 
 676     def c_03_hash(self, ctx):
 677         hash = hex(ctx.node())
 678         if hash in self.blacklist:
 679             self.error(ctx, "Blacklisted changeset: " + hash)
 680 
 681     def check(self, node):
 682         self.summarized = False
 683         self.cs_bugids = [ ]
 684         self.cs_author = None
 685         self.cs_reviewers = [ ]
 686         self.cs_contributor = None
 687         ctx = context.changectx(self.repo, node)
 688         self.ui.note(oneline(ctx))
 689         if hex(node) in changeset_whitelist:
 690             self.ui.note("%s in whitelist; skipping\n" % hex(node))
 691             return Pass
 692         for c in self.checks:
 693             cf = checker.__dict__[c]
 694             cf(self, ctx)
 695         return self.rv
 696 
 697     def check_repo(self):
 698 
 699         if not self.tags_lax:
 700             ts = self.repo.tags().keys()
 701             ignoredtypes = ['local']
 702             for t in ts:
 703                 if not tag_re.match(t) and not self.tagtype(t) in ignoredtypes:
 704                     self.error(None,
 705                                "Illegal tag name: %s" % t)
 706 
 707         bs = self.repo.branchmap()
 708         if len(bs) > 1:
 709             bs = bs.copy()
 710             del bs["default"]
 711             self.error(None,
 712                        "Named branches not permitted; this repository has: %s"
 713                        % ", ".join(bs.keys()))
 714 
 715         if self.strict:
 716             nh = len(self.repo.heads())
 717             if nh > 1:
 718                 self.error(None,
 719                            "Multiple heads not permitted; this repository has %d"
 720                            % nh)
 721 
 722         return self.rv
 723 
 724 
 725 def hook(ui, repo, hooktype, node=None, source=None, **opts):
 726     ui.debug("jcheck: node %s, source %s, args %s\n" % (node, source, opts))
 727     repocompat(repo)
 728     if not repo.local():
 729         raise error_Abort("repository '%s' is not local" % repo.path)
 730     if not os.path.exists(os.path.join(repo.root, ".jcheck")):
 731         ui.note("jcheck not enabled (no .jcheck in repository root); skipping\n")
 732         return Pass
 733     strict = opts.has_key("strict") and opts["strict"]
 734     lax = opts.has_key("lax") and opts["lax"]
 735     if strict:
 736         lax = False
 737     ch = checker(ui, repo, strict, lax)
 738     ch.check_repo()
 739     firstnode = bin(node)
 740     start = repo.changelog.rev(firstnode)
 741     end = (hasattr(repo.changelog, 'count') and repo.changelog.count() or
 742            len(repo.changelog))
 743     for rev in xrange(start, end):
 744         ch.check(repo.changelog.node(rev))
 745     if ch.rv == Fail:
 746         ui.status("\n")
 747     return ch.rv
 748 
 749 
 750 # Run this hook in repository gates
 751 
 752 def strict_hook(ui, repo, hooktype, node=None, source=None, **opts):
 753     opts["strict"] = True
 754     return hook(ui, repo, hooktype, node, source, **opts)
 755 
 756 # From Mercurial 1.9, the preferred way to define commands is using the @command
 757 # decorator. If this isn't available, fallback on a simple local implementation
 758 # that just adds the data to the cmdtable.
 759 cmdtable = {}
 760 if hasattr(registrar, 'command'):
 761     command = registrar.command(cmdtable)
 762 elif hasattr(cmdutil, 'command'):
 763     command = cmdutil.command(cmdtable)
 764 else:
 765     def command(name, options, synopsis):
 766         def decorator(func):
 767             cmdtable[name] = func, list(options), synopsis
 768             return func
 769         return decorator
 770 
 771 opts = [("", "lax", False, "Check comments, tags and whitespace laxly"),
 772         ("r", "rev", [], "check the specified revision or range (default: tip)"),
 773         ("s", "strict", False, "check everything")]
 774 
 775 help = "[-r rev] [-s]"
 776 
 777 @command("jcheck", opts, "hg jcheck " + help)
 778 def jcheck(ui, repo, **opts):
 779     """check changesets against JDK standards"""
 780     ui.debug("jcheck repo=%s opts=%s\n" % (repo.path, opts))
 781     repocompat(repo)
 782     if not repo.local():
 783         raise error_Abort("repository '%s' is not local" % repo.path)
 784     if not os.path.exists(os.path.join(repo.root, ".jcheck")):
 785         ui.status("jcheck not enabled (no .jcheck in repository root)\n")
 786         return Pass
 787     if len(opts["rev"]) == 0:
 788         opts["rev"] = ["tip"]
 789 
 790     strict = opts.has_key("strict") and opts["strict"]
 791     lax = opts.has_key("lax") and opts["lax"]
 792     if strict:
 793         lax = False
 794     ch = checker(ui, repo, strict, lax)
 795     ch.check_repo()
 796 
 797     try:
 798         nop = lambda c, fns: None
 799         iter = cmdutil.walkchangerevs(repo, _matchall(repo), opts, nop)
 800         for ctx in iter:
 801             ch.check(ctx.node())
 802     except (AttributeError, TypeError):
 803         # AttributeError:  matchall does not exist in hg < 1.1
 804         # TypeError:  walkchangerevs args differ in hg <= 1.3.1
 805         get = util.cachefunc(lambda r: repo.changectx(r).changeset())
 806         changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, opts)
 807         if ui.debugflag:
 808             displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
 809         for st, rev, fns in changeiter:
 810             if st == 'add':
 811                 node = repo.changelog.node(rev)
 812                 if ui.debugflag:
 813                     displayer.show(rev, node, copies=False)
 814                 ch.check(node)
 815             elif st == 'iter':
 816                 if ui.debugflag:
 817                     displayer.flush(rev)
 818 
 819     if ch.rv == Fail:
 820         ui.status("\n")
 821     return ch.rv
 822 
 823 # This is invoked on servers to check pushkeys; it's not needed on clients.
 824 def prepushkey(ui, repo, hooktype, namespace, key, old=None, new=None, **opts):
 825     if namespace == 'phases':
 826         return Pass
 827     ui.write_err('ERROR:  pushing keys (%s) is disabled\n' % namespace)
 828     return Fail