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