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, inspect, glob
  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         self.problem_list_patterns = []
 537         if self.conf.get("problem_list_patterns"):
 538             for pattern in self.conf.get("problem_list_patterns").split(":"):
 539                 self.problem_list_patterns.append(os.path.join(repo.root, *pattern.split("/")))
 540         if not self.bugids_ignore and not self.bugids_allow_dups:
 541             # only gather bug ids if we are going to use them
 542             self.repo_bugids = repo_bugids(ui, repo)
 543         self.blacklist = dict.fromkeys(changeset_blacklist)
 544         self.read_blacklist(blacklist_file)
 545         # hg < 1.0 does not have localrepo.tagtype()
 546         self.tagtype = getattr(self.repo, 'tagtype', lambda k: 'global')
 547 
 548     def read_blacklist(self, fname):
 549         if not os.path.exists(fname):
 550             return
 551         self.ui.debug('Reading blacklist file %s\n' % fname)
 552         f = open(fname)
 553         for line in f:
 554             # Any comment after the changeset hash becomes the dictionary value.
 555             l = [s.strip() for s in line.split('#', 1)]
 556             if l and l[0]:
 557                 self.blacklist[l[0]] = len(l) == 2 and l[1] or None
 558         f.close()
 559 
 560     def summarize(self, ctx):
 561         self.ui.status("\n")
 562         self.ui.status("> Changeset: %d:%s\n" % (ctx.rev(), short(ctx.node())))
 563         self.ui.status("> Author:    %s\n" % ctx.user())
 564         self.ui.status("> Date:      %s\n" % datestr(ctx))
 565         self.ui.status(">\n> ")
 566         self.ui.status("\n> ".join(ctx.description().splitlines()))
 567         self.ui.status("\n\n")
 568 
 569     def error(self, ctx, msg):
 570         if self.rv != Fail:
 571             self.ui.status("[jcheck %s %s]\n" % (_version, _date))
 572         if not self.summarized:
 573             if ctx:
 574                 self.summarize(ctx)
 575             else:
 576                 self.ui.status("\n")
 577             self.summarized = True
 578         self.ui.status(msg + "\n")
 579         self.rv = Fail
 580 
 581     def c_00_author(self, ctx):
 582         if not validate_author(self.ui, ctx.user(), self.conf["project"]):
 583             self.error(ctx, "Invalid changeset author: %s" % ctx.user())
 584         self.cs_author = ctx.user()
 585 
 586     def c_01_comment(self, ctx):
 587         m = badwhite_re.search(ctx.description())
 588         if m:
 589             ln = ctx.description().count("\n", 0, m.start()) + 1
 590             self.error(ctx, "%s in comment (line %d)" % (badwhite_what(m), ln))
 591 
 592         if is_merge(self.repo, ctx.rev()):
 593             if ctx.description() != "Merge":
 594                 self.error(ctx, ("Invalid comment for merge changeset"
 595                                  + " (must be \"Merge\")"))
 596             return
 597 
 598         if tag_desc_re.match(ctx.description()):
 599             ## Should check tag itself
 600             return
 601 
 602         if ((ctx.rev() == 0 or (ctx.rev() == 1 and self.comments_lax))
 603             and ctx.user() == "duke"
 604             and ctx.description().startswith("Initial load")):
 605             return
 606 
 607         lns = ctx.description().splitlines()
 608 
 609         # If lax, filter out non-matching lines
 610         if self.comments_lax:
 611             lns = filter(checked_comment_line, lns)
 612 
 613         i = 0                           # Input index
 614         gi = -1                         # Grammar index
 615         n = 0                           # Occurrence count
 616         while i < len(lns):
 617             gi = gi + 1
 618             if gi >= len(comment_grammar):
 619                 break
 620             ln = lns[i]
 621             st = comment_grammar[gi]
 622             n = 0
 623             while (st.ident_pattern.match(ln)):
 624                 m = st.check_pattern.match(ln)
 625                 if not m:
 626                     if not (st.name == "bugid line" and (self.bugids_lax or self.bugids_ignore)):
 627                         self.error(ctx, "Invalid %s" % st.name)
 628                 elif st.validator:
 629                     if not (st.name == "bugid line" and self.bugids_ignore):
 630                         st.validator(self, ctx, m, self.conf["project"])
 631                 n = n + 1
 632                 i = i + 1
 633                 if i >= len(lns):
 634                     break;
 635                 ln = lns[i]
 636             if n < st.min and not self.comments_lax:
 637                 self.error(ctx, "Incomplete comment: Missing %s" % st.name)
 638             if n > st.max:
 639                 self.error(ctx, "Too many %ss" % st.name)
 640 
 641         if not self.cs_contributor and [self.cs_author] == self.cs_reviewers:
 642             self.error(ctx, "Self-reviews not permitted")
 643         if not self.comments_lax:
 644             if (gi == 0 and n > 0):
 645                 self.error(ctx, "Incomplete comment: Missing bugid line")
 646             elif gi == 1 or (gi == 2 and n == 0):
 647                 self.error(ctx, "Incomplete comment: Missing reviewer attribution")
 648             if (i < len(lns)):
 649                 self.error(ctx, "Extraneous text in comment")
 650 
 651     def c_02_files(self, ctx):
 652         changes = self.repo.status(ctx.parents()[0].node(),
 653                                    ctx.node(), None)[:5]
 654         modified, added = changes[:2]
 655         # ## Skip files that were renamed but not modified
 656         files = modified + added
 657         if self.ui.debugflag:
 658             self.ui.debug("Checking files: %s\n" % ", ".join(files))
 659         for f in files:
 660             if ctx.rev() == 0:
 661                 ## This is loathsome
 662                 if f.startswith("test/java/rmi"): continue
 663                 if f.startswith("test/com/sun/javadoc/test"): continue
 664                 if f.startswith("docs/technotes/guides"): continue
 665             fx = ctx.filectx(f)
 666             if normext_re.match(f) and not self.whitespace_lax:
 667                 data = fx.data()
 668                 if "\t" in data or "\r" in data or " \n" in data:
 669                     m = badwhite_re.search(data)
 670                     if m:
 671                         ln = data.count("\n", 0, m.start()) + 1
 672                         self.error(ctx, "%s:%d: %s" % (f, ln, badwhite_what(m)))
 673             ## check_file_header(self, fx, data)
 674             flags = fx.manifest().flags(f)
 675             if 'x' in flags:
 676                 self.error(ctx, "%s: Executable files not permitted" % f)
 677             if 'l' in flags:
 678                 self.error(ctx, "%s: Symbolic links not permitted" % f)
 679 
 680     def c_03_hash(self, ctx):
 681         hash = hex(ctx.node())
 682         if hash in self.blacklist:
 683             self.error(ctx, "Blacklisted changeset: " + hash)
 684 
 685     def c_04_problem_list(self, ctx):
 686         if self.problem_list_patterns:
 687 
 688             def get_bugs_from_problem_list(list):
 689                 result = set()
 690                 with open(list) as f:
 691                     for line in f.readlines():
 692                         line = line.strip()
 693                         if not line.startswith('#'):
 694                             a = line.split(None, 2)
 695                             if len(a) > 1:
 696                                 bug = a[1]
 697                                 if bug.startswith('JDK-'):
 698                                     bug = bug[4:]
 699                                 if bug.isdigit():
 700                                     result.add(int(bug))
 701                 return result
 702 
 703             problem_lists = [ ]
 704             for pattern in self.problem_list_patterns:
 705                 problem_lists += glob.glob(pattern)
 706 
 707             if self.ui.debugflag:
 708                 self.ui.debug("Problem list files: %s\n" % ", ".join(problem_lists))
 709 
 710             problem_listed_bugs = set()
 711             for problem_list in problem_lists:
 712                 problem_listed_bugs = problem_listed_bugs.union(get_bugs_from_problem_list(problem_list))
 713 
 714             if self.ui.debugflag:
 715                 self.ui.debug("Currently problem listed bugs: %s\n" % ", ".join(str(b) for b in problem_listed_bugs))
 716 
 717             for b in self.cs_bugids:
 718                 if b in problem_listed_bugs:
 719                     self.error(ctx, "%d is still used in a problem list" % b)
 720 
 721     def check(self, rev, node):
 722         self.summarized = False
 723         self.cs_bugids = [ ]
 724         self.cs_author = None
 725         self.cs_reviewers = [ ]
 726         self.cs_contributor = None
 727         if len(inspect.getargspec(context.changectx.__init__).args) == 4:
 728             ctx = context.changectx(self.repo, rev, node)
 729         else:
 730             ctx = context.changectx(self.repo, node)
 731         self.ui.note(oneline(ctx))
 732         if hex(node) in changeset_whitelist:
 733             self.ui.note("%s in whitelist; skipping\n" % hex(node))
 734             return Pass
 735         for c in self.checks:
 736             cf = checker.__dict__[c]
 737             cf(self, ctx)
 738         return self.rv
 739 
 740     def check_repo(self):
 741 
 742         if not self.tags_lax:
 743             ts = self.repo.tags().keys()
 744             ignoredtypes = ['local']
 745             for t in ts:
 746                 if not tag_re.match(t) and not self.tagtype(t) in ignoredtypes:
 747                     self.error(None,
 748                                "Illegal tag name: %s" % t)
 749 
 750         bs = self.repo.branchmap()
 751         if len(bs) > 1:
 752             bs = bs.copy()
 753             del bs["default"]
 754             self.error(None,
 755                        "Named branches not permitted; this repository has: %s"
 756                        % ", ".join(bs.keys()))
 757 
 758         if self.strict:
 759             nh = len(self.repo.heads())
 760             if nh > 1:
 761                 self.error(None,
 762                            "Multiple heads not permitted; this repository has %d"
 763                            % nh)
 764 
 765         return self.rv
 766 
 767 
 768 def hook(ui, repo, hooktype, node=None, source=None, **opts):
 769     ui.debug("jcheck: node %s, source %s, args %s\n" % (node, source, opts))
 770     repocompat(repo)
 771     if not repo.local():
 772         raise error_Abort("repository '%s' is not local" % repo.path)
 773     if not os.path.exists(os.path.join(repo.root, ".jcheck")):
 774         ui.note("jcheck not enabled (no .jcheck in repository root); skipping\n")
 775         return Pass
 776     strict = opts.has_key("strict") and opts["strict"]
 777     lax = opts.has_key("lax") and opts["lax"]
 778     if strict:
 779         lax = False
 780     ch = checker(ui, repo, strict, lax)
 781     ch.check_repo()
 782     firstnode = bin(node)
 783     start = repo.changelog.rev(firstnode)
 784     end = (hasattr(repo.changelog, 'count') and repo.changelog.count() or
 785            len(repo.changelog))
 786     for rev in xrange(start, end):
 787         ch.check(rev, repo.changelog.node(rev))
 788     if ch.rv == Fail:
 789         ui.status("\n")
 790     return ch.rv
 791 
 792 
 793 # Run this hook in repository gates
 794 
 795 def strict_hook(ui, repo, hooktype, node=None, source=None, **opts):
 796     opts["strict"] = True
 797     return hook(ui, repo, hooktype, node, source, **opts)
 798 
 799 # From Mercurial 1.9, the preferred way to define commands is using the @command
 800 # decorator. If this isn't available, fallback on a simple local implementation
 801 # that just adds the data to the cmdtable.
 802 cmdtable = {}
 803 if hasattr(registrar, 'command'):
 804     command = registrar.command(cmdtable)
 805 elif hasattr(cmdutil, 'command'):
 806     command = cmdutil.command(cmdtable)
 807 else:
 808     def command(name, options, synopsis):
 809         def decorator(func):
 810             cmdtable[name] = func, list(options), synopsis
 811             return func
 812         return decorator
 813 
 814 opts = [("", "lax", False, "Check comments, tags and whitespace laxly"),
 815         ("r", "rev", [], "check the specified revision or range (default: tip)"),
 816         ("s", "strict", False, "check everything")]
 817 
 818 help = "[-r rev] [-s]"
 819 
 820 @command("jcheck", opts, "hg jcheck " + help)
 821 def jcheck(ui, repo, **opts):
 822     """check changesets against JDK standards"""
 823     ui.debug("jcheck repo=%s opts=%s\n" % (repo.path, opts))
 824     repocompat(repo)
 825     if not repo.local():
 826         raise error_Abort("repository '%s' is not local" % repo.path)
 827     if not os.path.exists(os.path.join(repo.root, ".jcheck")):
 828         ui.status("jcheck not enabled (no .jcheck in repository root)\n")
 829         return Pass
 830     if len(opts["rev"]) == 0:
 831         opts["rev"] = ["tip"]
 832 
 833     strict = opts.has_key("strict") and opts["strict"]
 834     lax = opts.has_key("lax") and opts["lax"]
 835     if strict:
 836         lax = False
 837     ch = checker(ui, repo, strict, lax)
 838     ch.check_repo()
 839 
 840     try:
 841         nop = lambda c, fns: None
 842         iter = cmdutil.walkchangerevs(repo, _matchall(repo), opts, nop)
 843         for ctx in iter:
 844             ch.check(ctx, ctx.node())
 845     except (AttributeError, TypeError):
 846         # AttributeError:  matchall does not exist in hg < 1.1
 847         # TypeError:  walkchangerevs args differ in hg <= 1.3.1
 848         get = util.cachefunc(lambda r: repo.changectx(r).changeset())
 849         changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, opts)
 850         if ui.debugflag:
 851             displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
 852         for st, rev, fns in changeiter:
 853             if st == 'add':
 854                 node = repo.changelog.node(rev)
 855                 if ui.debugflag:
 856                     displayer.show(rev, node, copies=False)
 857                 ch.check(rev, node)
 858             elif st == 'iter':
 859                 if ui.debugflag:
 860                     displayer.flush(rev)
 861 
 862     if ch.rv == Fail:
 863         ui.status("\n")
 864     return ch.rv
 865 
 866 # This is invoked on servers to check pushkeys; it's not needed on clients.
 867 def prepushkey(ui, repo, hooktype, namespace, key, old=None, new=None, **opts):
 868     if namespace == 'phases':
 869         return Pass
 870     ui.write_err('ERROR:  pushing keys (%s) is disabled\n' % namespace)
 871     return Fail