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