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