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