1 # 2 # Copyright (c) 2007, 2012, 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[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 if ui.debugflag: 248 ui.debug("Bugids: %s\n" % bugids) 249 return bugids 250 251 252 253 # Black/white lists 254 ## The black/white lists should really be in the database 255 256 # Bogus yet historically-accepted changesets, 257 # so that jcheck may evolve 258 # 259 changeset_whitelist = [ 260 '31000d79ec713de1e601dc16d74d726edd661ed5', 261 'b7987d19f5122a9f169e568f935b7cdf1a2609f5', 262 'c70a245cad3ad74602aa26b9d8e3d0472f7317c3', 263 'e8e20316458c1cdb85d9733a2e357e438a76a859', 264 'f68325221ce1efe94ab367400a49a8039d9b3db3', 265 '4dfa5d67c44500155ce9ab1e00d0de21bdbb9ee6', 266 '73a4d5be86497baf74c1fc194c9a0dd4e86d3a31', # jdk6/jdk6/jaxp bad comment 267 'a25f15bfd04b46a302b6ca1a298c176344f432dd', # jdk6/jdk6/jdk bad comment 268 'bf87d5af43614d609a5251c43eea44c028500d02', # jdk6/jdk6/jdk bad comment 269 'd77434402021cebc4c25b452db18bbfd2d7ccda1', # jdk6/jdk6/jdk bad comment 270 '931e5f39e365a0d550d79148ff87a7f9e864d2e1', # hotspot dup bug id 7147064 271 'd8abc90163a4b58db407a60cba331ab21c9977e7', # hotspot dup bug id 7147064 272 '45849c62c298aa8426c9e67599e4e35793d8db13', # pubs executable files 273 '38050e6655d8acc220800a28128cef328906e825', # pubs invalid bugid line 274 # hotspot/test/closed no Reviewed-by line 275 '8407fef5685f32ed42b79c5d5f13f6c8007171ac', 276 ] 277 278 # Bad changesets that should never be allowed in 279 # 280 changeset_blacklist = [ 281 'd3c74bae36884525be835ea428293bb6e7fa54d3', 282 '2bb5ef5c8a2dc0a32b1cd67803128ce12cad461e', 283 '4ff95cec682e67a374f9c5725d1879f43624d888', 284 '75f1884152db275047a09aa6085ae7c49e3f4126', 285 '6ecad8bfb1e5d34aef2ca61d30c1d197745d6844', 286 '4ec7d1890538c54a3cc7559e88db5a5e3371fe5d', 287 '669768c591ac438f4ca26d5cbdde7486ce49a2e2', 288 '2ded3bb1452943d5273e7b83af9609ce6511a105', 289 # hsdev/hotspot/{hotspot,master} dup bugid 7019157 290 '0d8777617a2d028ba0b82943c829a5c6623f1479', 291 # hsx/hotspot-comp/jdk dup bugid 7052202 + follow-on cset 292 'ad2d483067099421f3ea4492269cce69009b046f', 293 '521e2254994c76441c25f4374e16abbe314d8143', 294 # hsx/hotspot-rt/hotspot wrong bugid 7059288 + associated merge 295 'aa5f3f5978991182b8dbbd2b46fdcb47b6371dd9', 296 '709d9389b2bc290dad5a35ec5b5f951b07ce9631', 297 # jdk8/awt/jdk dup bugid 7100054 298 'f218e6bdf1e8e20ca3f0fdaeb29c38f56afdf988', 299 # jdk7u/jdk7u-dev/jaxp mistaken push 300 '24f4c1185305b60818d255550a0fdc1ddf52c2a6', 301 # jdk8/build/pubs executable file 302 '2528f2a1117000eb98891a139e8f839fc5e2bfab', 303 # jdk8/2d/jdk/src/closed security fix in wrong forest 304 '8c7fbf082af5ec6d7ad0b1789cedc98a597f1f83', 305 # jdk7u/jdk7u5/jdk bad fix for 6648202 306 'b06f6d9a6c329792401b954682d49169484b586d', 307 # hsx/hsx24/hotspot/src/closed bad merge 308 '306614eb47a79e6d25a8c7447d0fe47fac28b24c', 309 # hsx/hsx24/hotspot/test/closed bad merge 310 '96163ee390bf223fe0037739fc953e8ed7d49560', 311 # jdk8/awt/jdk INTJDK-7600365 312 '6be9b0bda6dccbfc738b9173a71a15dcafda4f3b', 313 # jdk8/tl/jdk/test/closed INTJDK-7600460 314 '9eb97a2b3274edff83a362f76bbadb866a97a89b', 315 # jdk7u11-dev/jaxp bad fix for 7192390 316 '1642814c94fd0206f8b4f460cc77fa6fc099731a', 317 # jdk7u11-dev/jdk bad fix for 7192390 318 '90eb0407ca69dc572d20490f17239b183bb616b6', 319 # jdk7u11-dev/jdk/test/closed bad fix for 7192390 320 '512af24c6909ef2c766c3a5217c719545de68bf7', 321 # jdk7u11-dev/jdk redone rmi fix 322 'fd6ce0200a7f519380e6863930e92f9182030fa0', 323 # jdk7u11-dev/jdk/test/closed redone rmi fix 324 '770d9cf0e1dc97f7aaa3fdfbb430b27a40b1a3a9', 325 # jdk7u13-dev/jdk bad fix for 8006611 326 '12b6a43f9fefa6c3bbf81d9096e764e72bacf065', 327 # jdk8/nashorn unwanted tag jdk8-b78 328 '8f49d8121c7e63d22f55412df4ff4800599686d6', 329 # hsx/hotspot-emb/hotspot wrong bugid 8009004 330 '29ab68ef5bb643f96218126dc2ff845561d552a4', 331 # jdk7u40/jdk/src/closed mistaken push 8016315 332 'd2b0a0c38c808bddff604a025469c5102a62edfe', 333 # jdk7u40/jdk/test/closed mistaken push 8016622 334 'd1e0d129aa0fccdc1ff1bcf20f126ffea900f30b', 335 # hsx/hotspot-gc/hotspot wrong bugid 8024547 336 '9561e0a8a2d63c45e751755d791e25396d94025a', 337 # jdk8/ds/install dup bugid 8024771 338 '835ef04a36e5fe95d4c0deb6e633371053f3cbba', 339 # jdk5u/jdk5.0u55/j2se bad fix 8025034 340 'e18574aa4be397b83a43e1444cb96c903f152fcb', 341 # jdk6u/jdk6u65/j2se bad fix 8025034 342 'a0f1589decc6181a5e048e48058d12cfa68cd3e1', 343 # jdk7u/jdk7u45/j2se bad fix 8025034 344 '0a312c04dbc8d33601efecfb0d23b8c09bf088fe', 345 # jdk8/build/pubs executable files 346 '3ecd3336c805978a37a933fbeca26c65fbe81432', 347 # hsx/jdk7u/hotspot wrong bugid 348 'f5d8e6d72e23d972db522f7ad4cd3b9b01085466', 349 # jdk8/tl/jdk erroneous push 7152892 350 'da4b0962ad1161dbd84e7daa0fdc706281c456a2', 351 # jdk8/tl/jdk/test/closed erroneous push 7152892 352 '1e69a1ce212c7c4c884f155dd123c936787db273', 353 # jdk9/jdk9/closed bad tag 354 '61fdebb503d79392536b8f502ae215022d1a1f1c', 355 # jdk9/hs-rt/jdk/src/closed dup bugid 8034951 356 'a19596796430761dde87bee9f6616480f1c93678', 357 # jdk9/hs-rt/jdk/test/closed dup bugid 8034951 358 'd2308c9714c94e87a0e60cda314746a5c17dbcc2', 359 # jdk9/client/deploy erroneous push 8041798 360 'fff4ff4fd6f031ab335b44842d69fd125297b5ab', 361 ] 362 363 # Path to file containing additional blacklisted changesets 364 blacklist_file = '/oj/db/hg/blacklist' 365 366 367 # Checker class 368 369 class checker(object): 370 371 def __init__(self, ui, repo, strict, lax): 372 self.ui = ui 373 self.repo = repo 374 self.rv = Pass 375 self.checks = [c for c in checker.__dict__ if c.startswith("c_")] 376 self.checks.sort() 377 self.summarized = False 378 self.repo_bugids = [ ] 379 self.cs_bugids = [ ] # Bugids in current changeset 380 self.cs_author = None # Author of current changeset 381 self.cs_reviewers = [ ] # Reviewers of current changeset 382 self.cs_contributor = None # Contributor of current changeset 383 self.strict = strict 384 self.conf = load_conf(repo.root) 385 self.whitespace_lax = lax and not strict 386 if self.conf.get("whitespace") == "lax": 387 self.whitespace_lax = True 388 self.comments_lax = lax and not strict 389 if self.conf.get("comments") == "lax": 390 self.comments_lax = True 391 self.tags_lax = lax and not strict 392 if self.conf.get("tags") == "lax": 393 self.tags_lax = True 394 self.bugids_allow_dups = self.conf.get("bugids") == "dup" 395 self.bugids_lax = lax and not strict 396 if self.conf.get("bugids") == "lax": 397 self.bugids_lax = True 398 self.bugids_ignore = False 399 if self.conf.get("bugids") == "ignore": 400 self.bugids_ignore = True 401 if not self.bugids_ignore: 402 # only identify bug ids if we are going to use them 403 self.repo_bugids = repo_bugids(ui, repo) 404 self.blacklist = dict.fromkeys(changeset_blacklist) 405 self.read_blacklist(blacklist_file) 406 # hg < 1.0 does not have localrepo.tagtype() 407 self.tagtype = getattr(self.repo, 'tagtype', lambda k: 'global') 408 409 def read_blacklist(self, fname): 410 if not os.path.exists(fname): 411 return 412 self.ui.debug('Reading blacklist file %s\n' % fname) 413 f = open(fname) 414 for line in f: 415 # Any comment after the changeset hash becomes the dictionary value. 416 l = [s.strip() for s in line.split('#', 1)] 417 if l and l[0]: 418 self.blacklist[l[0]] = len(l) == 2 and l[1] or None 419 f.close() 420 421 def summarize(self, ctx): 422 self.ui.status("\n") 423 self.ui.status("> Changeset: %d:%s\n" % (ctx.rev(), short(ctx.node()))) 424 self.ui.status("> Author: %s\n" % ctx.user()) 425 self.ui.status("> Date: %s\n" % datestr(ctx)) 426 self.ui.status(">\n> ") 427 self.ui.status("\n> ".join(ctx.description().splitlines())) 428 self.ui.status("\n\n") 429 430 def error(self, ctx, msg): 431 if self.rv != Fail: 432 self.ui.status("[jcheck %s %s]\n" % (_version, _date)) 433 if not self.summarized: 434 if ctx: 435 self.summarize(ctx) 436 else: 437 self.ui.status("\n") 438 self.summarized = True 439 self.ui.status(msg + "\n") 440 self.rv = Fail 441 442 def c_00_author(self, ctx): 443 self.ui.debug("author: %s\n" % ctx.user()) 444 if not validate_author(ctx.user(), self.conf["project"]): 445 self.error(ctx, "Invalid changeset author: %s" % ctx.user()) 446 self.cs_author = ctx.user() 447 448 def c_01_comment(self, ctx): 449 m = badwhite_re.search(ctx.description()) 450 if m: 451 ln = ctx.description().count("\n", 0, m.start()) + 1 452 self.error(ctx, "%s in comment (line %d)" % (badwhite_what(m), ln)) 453 454 if is_merge(self.repo, ctx.rev()): 455 if ctx.description() != "Merge": 456 self.error(ctx, ("Invalid comment for merge changeset" 457 + " (must be \"Merge\")")) 458 return 459 460 if tag_desc_re.match(ctx.description()): 461 ## Should check tag itself 462 return 463 464 if ((ctx.rev() == 0 or (ctx.rev() == 1 and self.comments_lax)) 465 and ctx.user() == "duke" 466 and ctx.description().startswith("Initial load")): 467 return 468 469 lns = ctx.description().splitlines() 470 471 # If lax, filter out non-matching lines 472 if self.comments_lax: 473 lns = filter(checked_comment_line, lns) 474 475 i = 0 # Input index 476 gi = -1 # Grammar index 477 n = 0 # Occurrence count 478 while i < len(lns): 479 gi = gi + 1 480 if gi >= len(comment_grammar): 481 break 482 ln = lns[i] 483 st = comment_grammar[gi] 484 n = 0 485 while (st.ident_pattern.match(ln)): 486 m = st.check_pattern.match(ln) 487 if not m: 488 if not (st.name == "bugid line" and (self.bugids_lax or self.bugids_ignore)): 489 self.error(ctx, "Invalid %s" % st.name) 490 elif st.validator: 491 if not (st.name == "bugid line" and self.bugids_ignore): 492 st.validator(self, ctx, m, self.conf["project"]) 493 n = n + 1 494 i = i + 1 495 if i >= len(lns): 496 break; 497 ln = lns[i] 498 if n < st.min and not self.comments_lax: 499 self.error(ctx, "Incomplete comment: Missing %s" % st.name) 500 if n > st.max: 501 self.error(ctx, "Too many %ss" % st.name) 502 503 if not self.cs_contributor and [self.cs_author] == self.cs_reviewers: 504 self.error(ctx, "Self-reviews not permitted") 505 if not self.comments_lax: 506 if (gi == 0 and n > 0): 507 self.error(ctx, "Incomplete comment: Missing bugid line") 508 elif gi == 1 or (gi == 2 and n == 0): 509 self.error(ctx, "Incomplete comment: Missing reviewer attribution") 510 if (i < len(lns)): 511 self.error(ctx, "Extraneous text in comment") 512 513 def c_02_files(self, ctx): 514 changes = self.repo.status(ctx.parents()[0].node(), 515 ctx.node(), None)[:5] 516 modified, added = changes[:2] 517 # ## Skip files that were renamed but not modified 518 files = modified + added 519 if self.ui.debugflag: 520 self.ui.debug("Checking files: %s\n" % ", ".join(files)) 521 for f in files: 522 if ctx.rev() == 0: 523 ## This is loathsome 524 if f.startswith("test/java/rmi"): continue 525 if f.startswith("test/com/sun/javadoc/test"): continue 526 if f.startswith("docs/technotes/guides"): continue 527 fx = ctx.filectx(f) 528 if normext_re.match(f) and not self.whitespace_lax: 529 data = fx.data() 530 m = badwhite_re.search(data) 531 if m: 532 ln = data.count("\n", 0, m.start()) + 1 533 self.error(ctx, "%s:%d: %s" % (f, ln, badwhite_what(m))) 534 ## check_file_header(self, fx, data) 535 flags = fx.manifest().flags(f) 536 if 'x' in flags: 537 self.error(ctx, "%s: Executable files not permitted" % f) 538 if 'l' in flags: 539 self.error(ctx, "%s: Symbolic links not permitted" % f) 540 541 def c_03_hash(self, ctx): 542 hash = hex(ctx.node()) 543 if hash in self.blacklist: 544 self.error(ctx, "Blacklisted changeset: " + hash) 545 546 def check(self, node): 547 self.summarized = False 548 self.cs_bugids = [ ] 549 self.cs_author = None 550 self.cs_reviewers = [ ] 551 self.cs_contributor = None 552 ctx = context.changectx(self.repo, node) 553 self.ui.note(oneline(ctx)) 554 if hex(node) in changeset_whitelist: 555 self.ui.note("%s in whitelist; skipping\n" % hex(node)) 556 return Pass 557 for c in self.checks: 558 cf = checker.__dict__[c] 559 cf(self, ctx) 560 return self.rv 561 562 def check_repo(self): 563 564 if not self.tags_lax: 565 ts = self.repo.tags().keys() 566 ignoredtypes = ['local'] 567 for t in ts: 568 if not tag_re.match(t) and not self.tagtype(t) in ignoredtypes: 569 self.error(None, 570 "Illegal tag name: %s" % t) 571 572 bs = self.repo.branchmap() 573 if len(bs) > 1: 574 bs = bs.copy() 575 del bs["default"] 576 self.error(None, 577 "Named branches not permitted; this repository has: %s" 578 % ", ".join(bs.keys())) 579 580 if self.strict: 581 nh = len(self.repo.heads()) 582 if nh > 1: 583 self.error(None, 584 "Multiple heads not permitted; this repository has %d" 585 % nh) 586 587 return self.rv 588 589 590 def hook(ui, repo, hooktype, node=None, source=None, **opts): 591 ui.debug("jcheck: node %s, source %s, args %s\n" % (node, source, opts)) 592 repocompat(repo) 593 if not repo.local(): 594 raise util.Abort("repository '%s' is not local" % repo.path) 595 if not os.path.exists(os.path.join(repo.root, ".jcheck")): 596 ui.note("jcheck not enabled (no .jcheck in repository root); skipping\n") 597 return Pass 598 strict = opts.has_key("strict") and opts["strict"] 599 lax = opts.has_key("lax") and opts["lax"] 600 if strict: 601 lax = False 602 ch = checker(ui, repo, strict, lax) 603 ch.check_repo() 604 firstnode = bin(node) 605 start = repo.changelog.rev(firstnode) 606 end = (hasattr(repo.changelog, 'count') and repo.changelog.count() or 607 len(repo.changelog)) 608 for rev in xrange(start, end): 609 ch.check(repo.changelog.node(rev)) 610 if ch.rv == Fail: 611 ui.status("\n") 612 return ch.rv 613 614 615 # Run this hook in repository gates 616 617 def strict_hook(ui, repo, hooktype, node=None, source=None, **opts): 618 opts["strict"] = True 619 return hook(ui, repo, hooktype, node, source, **opts) 620 621 622 def jcheck(ui, repo, **opts): 623 """check changesets against JDK standards""" 624 ui.debug("jcheck repo=%s opts=%s\n" % (repo.path, opts)) 625 repocompat(repo) 626 if not repo.local(): 627 raise util.Abort("repository '%s' is not local" % repo.path) 628 if not os.path.exists(os.path.join(repo.root, ".jcheck")): 629 ui.status("jcheck not enabled (no .jcheck in repository root)\n") 630 return Pass 631 if len(opts["rev"]) == 0: 632 opts["rev"] = ["tip"] 633 634 strict = opts.has_key("strict") and opts["strict"] 635 lax = opts.has_key("lax") and opts["lax"] 636 if strict: 637 lax = False 638 ch = checker(ui, repo, strict, lax) 639 ch.check_repo() 640 641 try: 642 nop = lambda c, fns: None 643 iter = cmdutil.walkchangerevs(repo, _matchall(repo), opts, nop) 644 for ctx in iter: 645 ch.check(ctx.node()) 646 except (AttributeError, TypeError): 647 # AttributeError: matchall does not exist in hg < 1.1 648 # TypeError: walkchangerevs args differ in hg <= 1.3.1 649 get = util.cachefunc(lambda r: repo.changectx(r).changeset()) 650 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, opts) 651 if ui.debugflag: 652 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn) 653 for st, rev, fns in changeiter: 654 if st == 'add': 655 node = repo.changelog.node(rev) 656 if ui.debugflag: 657 displayer.show(rev, node, copies=False) 658 ch.check(node) 659 elif st == 'iter': 660 if ui.debugflag: 661 displayer.flush(rev) 662 663 if ch.rv == Fail: 664 ui.status("\n") 665 return ch.rv 666 667 # This is invoked on servers to check pushkeys; it's not needed on clients. 668 def prepushkey(ui, repo, hooktype, namespace, key, old=None, new=None, **opts): 669 if namespace == 'phases': 670 return Pass 671 ui.write_err('ERROR: pushing keys (%s) is disabled\n' % namespace) 672 return Fail 673 674 opts = [("", "lax", False, "Check comments, tags and whitespace laxly"), 675 ("r", "rev", [], "check the specified revision or range (default: tip)"), 676 ("s", "strict", False, "check everything")] 677 678 help = "[-r rev] [-s]" 679 680 cmdtable = { 681 "jcheck": (jcheck, opts, "hg jcheck " + help) 682 }