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