1# 2# This program is free software; you can redistribute it and/or modify 3# it under the terms of the GNU General Public License version 2 4# as published by the Free Software Foundation. 5# 6# This program is distributed in the hope that it will be useful, 7# but WITHOUT ANY WARRANTY; without even the implied warranty of 8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9# GNU General Public License for more details. 10# 11# You should have received a copy of the GNU General Public License 12# along with this program; if not, write to the Free Software 13# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 14# 15 16# 17# Copyright 2009 Sun Microsystems, Inc. All rights reserved. 18# Use is subject to license terms. 19# 20 21'''workspace extensions for mercurial 22 23This extension contains a number of commands to help you work within 24the OpenSolaris consolidations. 25 26Common uses: 27 28Show diffs relative to parent workspace - pdiffs 29Check source style rules - nits 30Run pre-putback checks - pbchk 31Collapse all your changes into a single changeset - recommit''' 32 33 34# 35# NB: This assumes the normal directory structure, with this 36# extension 2 levels below .../lib/python. 37# 38# If you change that, change this 39# 40import sys, os, stat, termios, atexit 41sys.path.insert(1, "%s/../../" % os.path.dirname(__file__)) 42 43from onbld.Scm import Version 44from mercurial import util 45 46try: 47 Version.check_version() 48except Version.VersionMismatch, badversion: 49 raise util.Abort("Version Mismatch:\n %s\n" % badversion) 50 51import ConfigParser 52from mercurial import cmdutil, node, ignore 53 54from onbld.Scm.WorkSpace import WorkSpace, ActiveEntry 55from onbld.Scm.Backup import CdmBackup 56from onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk 57from onbld.Checks import JStyle, Keywords, Mapfile, Rti, onSWAN 58 59 60def yes_no(ui, msg, default): 61 if default: 62 prompt = ' [Y/n]:' 63 defanswer = 'y' 64 else: 65 prompt = ' [y/N]:' 66 defanswer = 'n' 67 68 resp = ui.prompt(msg + prompt, r'([Yy(es)?|[Nn]o?)?', 69 default=defanswer) 70 if not resp: 71 return default 72 elif resp[0] in ['Y', 'y']: 73 return True 74 else: 75 return False 76 77 78def _buildfilelist(repo, args): 79 '''build a list of files in which we're interested 80 81 If no files are specified, then we'll default to using 82 the entire active list. 83 84 Returns a dictionary, wherein the keys are cwd-relative file paths, 85 and the values (when present) are entries from the active list. 86 Instead of warning the user explicitly about files not in the active 87 list, we'll attempt to do checks on them.''' 88 89 fdict = {} 90 91 # 92 # If the user specified files on the command line, we'll only check 93 # those files. We won't pull the active list at all. That means we 94 # won't be smart about skipping deleted files and such, so the user 95 # needs to be smart enough to not explicitly specify a nonexistent 96 # file path. Which seems reasonable. 97 # 98 if args: 99 for f in args: 100 fdict[f] = None 101 102 # 103 # Otherwise, if no files were listed explicitly, we assume that the 104 # checks should be run on all files in the active list. So we determine 105 # it here. 106 # 107 # Tracking the file paths is a slight optimization, in that multiple 108 # check functions won't need to derive it themselves. This also dovetails 109 # nicely with the expectation that explicitly specified files will be 110 # ${CWD}-relative paths, so the fdict keyspace will be consistent either 111 # way. 112 # 113 else: 114 active = wslist[repo].active() 115 for e in sorted(active): 116 fdict[wslist[repo].filepath(e.name)] = e 117 118 return fdict 119 120 121def not_check(repo, cmd): 122 '''return a function which returns boolean indicating whether a file 123 should be skipped for CMD.''' 124 125 # 126 # The ignore routines need a canonical path to the file (relative to the 127 # repo root), whereas the check commands get paths relative to the cwd. 128 # 129 # Wrap our argument such that the path is canonified before it is checked. 130 # 131 def canonified_check(ignfunc): 132 def f(path): 133 cpath = util.canonpath(repo.root, repo.getcwd(), path) 134 return ignfunc(cpath) 135 return f 136 137 ignorefiles = [] 138 139 for f in [repo.join('cdm/%s.NOT' % cmd), 140 repo.wjoin('exception_lists/%s' % cmd)]: 141 if os.path.exists(f): 142 ignorefiles.append(f) 143 144 if ignorefiles: 145 ign = ignore.ignore(repo.root, ignorefiles, repo.ui.warn) 146 return canonified_check(ign) 147 else: 148 return util.never 149 150 151# 152# Adding a reference to WorkSpace from a repo causes a circular reference 153# repo <-> WorkSpace. 154# 155# This prevents repo, WorkSpace and members thereof from being garbage 156# collected. Since transactions are aborted when the transaction object 157# is collected, and localrepo holds a reference to the most recently created 158# transaction, this prevents transactions from cleanly aborting. 159# 160# Instead, we hold the repo->WorkSpace association in a dictionary, breaking 161# that dependence. 162# 163wslist = {} 164 165 166def reposetup(ui, repo): 167 if repo.local() and repo not in wslist: 168 wslist[repo] = WorkSpace(repo) 169 170 if ui.interactive and sys.stdin.isatty(): 171 ui.setconfig('hooks', 'preoutgoing.cdm_pbconfirm', 172 'python:hgext_cdm.pbconfirm') 173 174 175def pbconfirm(ui, repo, hooktype, source): 176 def wrapper(settings=None): 177 termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings) 178 179 if source == 'push': 180 if not yes_no(ui, "Are you sure you wish to push?", False): 181 return 1 182 else: 183 settings = termios.tcgetattr(sys.stdin.fileno()) 184 orig = list(settings) 185 atexit.register(wrapper, orig) 186 settings[3] = settings[3] & (~termios.ISIG) # c_lflag 187 termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings) 188 189 190def cdm_pdiffs(ui, repo, *pats, **opts): 191 '''list workspace diffs relative to parent workspace 192 193 The parent tip is taken to be the latest revision shared between 194 us and the parent workspace.''' 195 196 parent = opts['parent'] 197 198 diffs = wslist[repo].pdiff(pats, opts, parent=parent) 199 if diffs: 200 ui.write(diffs) 201 202 203def cdm_list(ui, repo, **opts): 204 '''list files changed relative to parent workspace 205 206 The parent tip is taken to be the latest revision shared between 207 us and the parent workspace.''' 208 209 wanted = [] 210 211 if opts['added']: 212 wanted.append(ActiveEntry.ADDED) 213 if opts['modified']: 214 wanted.append(ActiveEntry.MODIFIED) 215 if opts['removed']: 216 wanted.append(ActiveEntry.REMOVED) 217 218 act = wslist[repo].active(opts['parent']) 219 chngmap = {ActiveEntry.MODIFIED: 'modified', 220 ActiveEntry.ADDED: 'added', 221 ActiveEntry.REMOVED: 'removed'} 222 223 lst = {} 224 for entry in act: 225 if wanted and (entry.change not in wanted): 226 continue 227 228 chngstr = chngmap[entry.change] 229 if chngstr not in lst: 230 lst[chngstr] = [] 231 lst[chngstr].append(entry) 232 233 for chng in sorted(lst.keys()): 234 ui.write(chng + ':\n') 235 for elt in sorted(lst[chng]): 236 if elt.is_renamed(): 237 ui.write('\t%s (renamed from %s)\n' % (elt.name, 238 elt.parentname)) 239 elif elt.is_copied(): 240 ui.write('\t%s (copied from %s)\n' % (elt.name, 241 elt.parentname)) 242 else: 243 ui.write('\t%s\n' % elt.name) 244 245 246def cdm_arcs(ui, repo, parent=None): 247 'show all ARC cases in checkin comments' 248 act = wslist[repo].active(parent) 249 250 # We take a set of the appropriate comments to eliminate duplicates. 251 for elt in set(filter(Comments.isARC, act.comments())): 252 ui.write(elt + '\n') 253 254 255def cdm_bugs(ui, repo, parent=None): 256 'show all bug IDs in checkin comments' 257 act = wslist[repo].active(parent) 258 259 for elt in set(filter(Comments.isBug, act.comments())): 260 ui.write(elt + '\n') 261 262 263def cdm_comments(ui, repo, parent=None): 264 'show checkin comments for active files' 265 act = wslist[repo].active(parent) 266 267 for elt in act.comments(): 268 ui.write(elt + '\n') 269 270 271def cdm_renamed(ui, repo, parent=None): 272 '''show renamed active files 273 274 Renamed files are shown in the format 275 276 newname oldname 277 278 One pair per-line.''' 279 280 act = wslist[repo].active(parent) 281 282 for entry in sorted(filter(lambda x: x.is_renamed(), act)): 283 ui.write('%s %s\n' % (entry.name, entry.parentname)) 284 285 286def cdm_comchk(ui, repo, **opts): 287 '''check checkin comments for active files 288 289 Check that checkin comments conform to O/N rules.''' 290 291 active = wslist[repo].active(opts.get('parent')) 292 293 ui.write('Comments check:\n') 294 295 check_db = not opts.get('nocheck') 296 return Comments.comchk(active.comments(), check_db=check_db, output=ui, 297 arcPath=ui.config('cdm', 'arcpath', None)) 298 299 300def cdm_cddlchk(ui, repo, *args, **opts): 301 '''check for a valid CDDL block in active files 302 303 See http://www.opensolaris.org/os/community/on/devref_toc/devref_7/#7_2_3_nonformatting_considerations 304 for more info.''' 305 306 filelist = opts.get('filelist') or _buildfilelist(repo, args) 307 308 ui.write('CDDL block check:\n') 309 310 lenient = True 311 ret = 0 312 313 exclude = not_check(repo, 'cddlchk') 314 315 for f, e in filelist.iteritems(): 316 if e and e.is_removed(): 317 continue 318 elif (e or opts.get('honour_nots')) and exclude(f): 319 ui.status('Skipping %s...\n' % f) 320 continue 321 elif e and e.is_added(): 322 lenient = False 323 else: 324 lenient = True 325 326 fh = open(f, 'r') 327 ret |= Cddl.cddlchk(fh, lenient=lenient, output=ui) 328 fh.close() 329 return ret 330 331 332def cdm_mapfilechk(ui, repo, *args, **opts): 333 '''check for a valid MAPFILE header block in active files 334 335 Check that all link-editor mapfiles contain the standard mapfile 336 header comment directing the reader to the document containing 337 Solaris object versioning rules (README.mapfile).''' 338 339 filelist = opts.get('filelist') or _buildfilelist(repo, args) 340 341 ui.write('Mapfile comment check:\n') 342 343 ret = 0 344 exclude = not_check(repo, 'mapfilechk') 345 346 for f, e in filelist.iteritems(): 347 if e and e.is_removed(): 348 continue 349 elif f.find('mapfile') == -1: 350 continue 351 elif (e or opts.get('honour_nots')) and exclude(f): 352 ui.status('Skipping %s...\n' % f) 353 continue 354 355 fh = open(f, 'r') 356 ret |= Mapfile.mapfilechk(fh, output=ui) 357 fh.close() 358 return ret 359 360 361def cdm_copyright(ui, repo, *args, **opts): 362 '''check active files for valid copyrights 363 364 Check that all active files have a valid copyright containing the 365 current year (and *only* the current year). 366 See http://www.opensolaris.org/os/project/muskoka/on_dev/golden_rules.txt 367 for more info.''' 368 369 filelist = opts.get('filelist') or _buildfilelist(repo, args) 370 371 ui.write('Copyright check:\n') 372 373 ret = 0 374 exclude = not_check(repo, 'copyright') 375 376 for f, e in filelist.iteritems(): 377 if e and e.is_removed(): 378 continue 379 elif (e or opts.get('honour_nots')) and exclude(f): 380 ui.status('Skipping %s...\n' % f) 381 continue 382 383 fh = open(f, 'r') 384 ret |= Copyright.copyright(fh, output=ui) 385 fh.close() 386 return ret 387 388 389def cdm_hdrchk(ui, repo, *args, **opts): 390 '''check active header files conform to O/N rules''' 391 392 filelist = opts.get('filelist') or _buildfilelist(repo, args) 393 394 ui.write('Header format check:\n') 395 396 ret = 0 397 exclude = not_check(repo, 'hdrchk') 398 399 for f, e in filelist.iteritems(): 400 if e and e.is_removed(): 401 continue 402 elif not f.endswith('.h'): 403 continue 404 elif (e or opts.get('honour_nots')) and exclude(f): 405 ui.status('Skipping %s...\n' % f) 406 continue 407 408 fh = open(f, 'r') 409 ret |= HdrChk.hdrchk(fh, lenient=True, output=ui) 410 fh.close() 411 return ret 412 413 414def cdm_cstyle(ui, repo, *args, **opts): 415 '''check active C source files conform to the C Style Guide 416 417 See http://opensolaris.org/os/community/documentation/getting_started_docs/cstyle.ms.pdf''' 418 419 filelist = opts.get('filelist') or _buildfilelist(repo, args) 420 421 ui.write('C style check:\n') 422 423 ret = 0 424 exclude = not_check(repo, 'cstyle') 425 426 for f, e in filelist.iteritems(): 427 if e and e.is_removed(): 428 continue 429 elif not (f.endswith('.c') or f.endswith('.h')): 430 continue 431 elif (e or opts.get('honour_nots')) and exclude(f): 432 ui.status('Skipping %s...\n' % f) 433 continue 434 435 fh = open(f, 'r') 436 ret |= CStyle.cstyle(fh, output=ui, 437 picky=True, check_posix_types=True, 438 check_continuation=True) 439 fh.close() 440 return ret 441 442 443def cdm_jstyle(ui, repo, *args, **opts): 444 'check active Java source files for common stylistic errors' 445 446 filelist = opts.get('filelist') or _buildfilelist(repo, args) 447 448 ui.write('Java style check:\n') 449 450 ret = 0 451 exclude = not_check(repo, 'jstyle') 452 453 for f, e in filelist.iteritems(): 454 if e and e.is_removed(): 455 continue 456 elif not f.endswith('.java'): 457 continue 458 elif (e or opts.get('honour_nots')) and exclude(f): 459 ui.status('Skipping %s...\n' % f) 460 continue 461 462 fh = open(f, 'r') 463 ret |= JStyle.jstyle(fh, output=ui, picky=True) 464 fh.close() 465 return ret 466 467 468def cdm_permchk(ui, repo, *args, **opts): 469 '''check active files permission - warn +x (execute) mode''' 470 471 filelist = opts.get('filelist') or _buildfilelist(repo, args) 472 473 ui.write('File permission check:\n') 474 475 exeFiles = [] 476 exclude = not_check(repo, 'permchk') 477 478 for f, e in filelist.iteritems(): 479 if e and e.is_removed(): 480 continue 481 elif (e or opts.get('honour_nots')) and exclude(f): 482 ui.status('Skipping %s...\n' % f) 483 continue 484 485 mode = stat.S_IMODE(os.stat(f)[stat.ST_MODE]) 486 if mode & stat.S_IEXEC: 487 exeFiles.append(f) 488 489 if len(exeFiles) > 0: 490 ui.write('Warning: the following active file(s) have executable mode ' 491 '(+x) permission set,\nremove unless intentional:\n') 492 for fname in exeFiles: 493 ui.write(" %s\n" % fname) 494 495 return len(exeFiles) > 0 496 497 498def cdm_tagchk(ui, repo, **opts): 499 '''check if .hgtags is active and issue warning 500 501 Tag sharing among repositories is restricted to gatekeepers''' 502 503 active = wslist[repo].active(opts.get('parent')) 504 505 ui.write('Checking for new tags:\n') 506 507 if ".hgtags" in active: 508 tfile = wslist[repo].filepath('.hgtags') 509 ptip = active.parenttip.rev() 510 511 ui.write('Warning: Workspace contains new non-local tags.\n' 512 'Only gatekeepers should add or modify such tags.\n' 513 'Use the following commands to revert these changes:\n' 514 ' hg revert -r%d %s\n' 515 ' hg commit %s\n' 516 'You should also recommit before integration\n' % 517 (ptip, tfile, tfile)) 518 519 return 1 520 521 return 0 522 523 524def cdm_branchchk(ui, repo, **opts): 525 '''check if multiple heads (or branches) are present, or if 526 branch changes are made''' 527 528 ui.write('Checking for multiple heads (or branches):\n') 529 530 heads = set(repo.heads()) 531 parents = set([x.node() for x in wslist[repo].workingctx().parents()]) 532 533 # 534 # We care if there's more than one head, and those heads aren't 535 # identical to the dirstate parents (if they are identical, it's 536 # an uncommitted merge which mergechk will catch, no need to 537 # complain twice). 538 # 539 if len(heads) > 1 and heads != parents: 540 ui.write('Workspace has multiple heads (or branches):\n') 541 for head in [repo.changectx(head) for head in heads]: 542 ui.write(" %d:%s\t%s\n" % 543 (head.rev(), str(head), head.description().splitlines()[0])) 544 ui.write('You must merge and recommit.\n') 545 return 1 546 547 ui.write('\nChecking for branch changes:\n') 548 549 if repo.dirstate.branch() != 'default': 550 ui.write("Warning: Workspace tip has named branch: '%s'\n" 551 "Only gatekeepers should push new branches.\n" 552 "Use the following commands to restore the branch name:\n" 553 " hg branch [-f] default\n" 554 " hg commit\n" 555 "You should also recommit before integration\n" % 556 (repo.dirstate.branch())) 557 return 1 558 559 branches = repo.branchtags().keys() 560 if len(branches) > 1: 561 ui.write('Warning: Workspace has named branches:\n') 562 for t in branches: 563 if t == 'default': 564 continue 565 ui.write("\t%s\n" % t) 566 567 ui.write("Only gatekeepers should push new branches.\n" 568 "Use the following commands to remove extraneous branches.\n" 569 " hg branch [-f] default\n" 570 " hg commit" 571 "You should also recommit before integration\n") 572 return 1 573 574 return 0 575 576 577def cdm_rtichk(ui, repo, **opts): 578 '''check active bug/RFEs for approved RTIs 579 580 Only works on SWAN.''' 581 582 if opts.get('nocheck') or os.path.exists(repo.join('cdm/rtichk.NOT')): 583 ui.status('Skipping RTI checks...\n') 584 return 0 585 586 if not onSWAN(): 587 ui.write('RTI checks only work on SWAN, skipping...\n') 588 return 0 589 590 parent = wslist[repo].parent(opts.get('parent')) 591 active = wslist[repo].active(parent) 592 593 ui.write('RTI check:\n') 594 595 bugs = [] 596 597 for com in active.comments(): 598 match = Comments.isBug(com) 599 if match and match.group(1) not in bugs: 600 bugs.append(match.group(1)) 601 602 # RTI normalizes the gate path for us 603 return int(not Rti.rti(bugs, gatePath=parent, output=ui)) 604 605 606def cdm_keywords(ui, repo, *args, **opts): 607 '''check source files do not contain SCCS keywords''' 608 609 filelist = opts.get('filelist') or _buildfilelist(repo, args) 610 611 ui.write('Keywords check:\n') 612 613 ret = 0 614 exclude = not_check(repo, 'keywords') 615 616 for f, e in filelist.iteritems(): 617 if e and e.is_removed(): 618 continue 619 elif (e or opts.get('honour_nots')) and exclude(f): 620 ui.status('Skipping %s...\n' % f) 621 continue 622 623 fh = open(f, 'r') 624 ret |= Keywords.keywords(fh, output=ui) 625 fh.close() 626 return ret 627 628 629# 630# NB: 631# There's no reason to hook this up as an invokable command, since 632# we have 'hg status', but it must accept the same arguments. 633# 634def cdm_outchk(ui, repo, **opts): 635 '''Warn the user if they have uncommitted changes''' 636 637 ui.write('Checking for uncommitted changes:\n') 638 639 st = wslist[repo].modified() 640 if st: 641 ui.write('Warning: the following files have uncommitted changes:\n') 642 for elt in st: 643 ui.write(' %s\n' % elt) 644 return 1 645 return 0 646 647 648def cdm_mergechk(ui, repo, **opts): 649 '''Warn the user if their workspace contains merges''' 650 651 active = wslist[repo].active(opts.get('parent')) 652 653 ui.write('Checking for merges:\n') 654 655 merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1], 656 active.revs) 657 658 if merges: 659 ui.write('Workspace contains the following merges:\n') 660 for rev in merges: 661 desc = rev.description().splitlines() 662 ui.write(' %s:%s\t%s\n' % 663 (rev.rev() or "working", str(rev), 664 desc and desc[0] or "*** uncommitted change ***")) 665 return 1 666 return 0 667 668 669def run_checks(ws, cmds, *args, **opts): 670 '''Run CMDS (with OPTS) over active files in WS''' 671 672 ret = 0 673 674 flist = _buildfilelist(ws.repo, args) 675 676 for cmd in cmds: 677 name = cmd.func_name.split('_')[1] 678 if not ws.ui.configbool('cdm', name, True): 679 ws.ui.status('Skipping %s check...\n' % name) 680 else: 681 ws.ui.pushbuffer() 682 683 result = cmd(ws.ui, ws.repo, filelist=flist, 684 honour_nots=True, *args, **opts) 685 ret |= result 686 687 output = ws.ui.popbuffer() 688 if not ws.ui.quiet or result != 0: 689 ws.ui.write(output, '\n') 690 return ret 691 692 693def cdm_nits(ui, repo, *args, **opts): 694 '''check for stylistic nits in active files 695 696 Run cddlchk, copyright, cstyle, hdrchk, jstyle, mapfilechk, 697 permchk, and keywords checks.''' 698 699 cmds = [cdm_cddlchk, 700 cdm_copyright, 701 cdm_cstyle, 702 cdm_hdrchk, 703 cdm_jstyle, 704 cdm_mapfilechk, 705 cdm_permchk, 706 cdm_keywords] 707 708 return run_checks(wslist[repo], cmds, *args, **opts) 709 710 711def cdm_pbchk(ui, repo, **opts): 712 '''pre-putback check all active files 713 714 Run cddlchk, comchk, copyright, cstyle, hdrchk, jstyle, mapfilechk, 715 permchk, tagchk, branchchk, keywords and rtichk checks. Additionally, 716 warn about uncommitted changes.''' 717 718 # 719 # The current ordering of these is that the commands from cdm_nits 720 # run first in the same order as they would in cdm_nits. Then the 721 # pbchk specifics run 722 # 723 cmds = [cdm_cddlchk, 724 cdm_copyright, 725 cdm_cstyle, 726 cdm_hdrchk, 727 cdm_jstyle, 728 cdm_mapfilechk, 729 cdm_permchk, 730 cdm_keywords, 731 cdm_comchk, 732 cdm_tagchk, 733 cdm_branchchk, 734 cdm_rtichk, 735 cdm_outchk, 736 cdm_mergechk] 737 738 return run_checks(wslist[repo], cmds, **opts) 739 740 741def cdm_recommit(ui, repo, **opts): 742 '''compact outgoing deltas into a single, conglomerate delta''' 743 744 if not os.getcwd().startswith(repo.root): 745 raise util.Abort('recommit is not safe to run with -R') 746 747 if wslist[repo].modified(): 748 raise util.Abort('workspace has uncommitted changes') 749 750 if wslist[repo].merged(): 751 raise util.Abort('workspace contains uncommitted merge') 752 753 if wslist[repo].branched(): 754 raise util.Abort('workspace contains uncommitted branch') 755 756 if wslist[repo].mq_applied(): 757 raise util.Abort("workspace has Mq patches applied") 758 759 wlock = repo.wlock() 760 lock = repo.lock() 761 762 heads = repo.heads() 763 if len(heads) > 1: 764 ui.warn('Workspace has multiple heads (or branches):\n') 765 for head in heads: 766 ui.warn('\t%d\n' % repo.changelog.rev(head)) 767 raise util.Abort('you must merge before recommitting') 768 769 active = wslist[repo].active(opts['parent']) 770 771 if len(active.revs) <= 0: 772 raise util.Abort("no changes to recommit") 773 774 if len(active.files()) <= 0: 775 ui.warn("Recommitting %d active changesets, but no active files\n" % 776 len(active.revs)) 777 778 # 779 # During the course of a recommit, any file bearing a name matching the 780 # source name of any renamed file will be clobbered by the operation. 781 # 782 # As such, we ask the user before proceeding. 783 # 784 bogosity = [f.parentname for f in active if f.is_renamed() and 785 os.path.exists(repo.wjoin(f.parentname))] 786 787 if bogosity: 788 ui.warn("The following file names are the original name of a rename " 789 "and also present\n" 790 "in the working directory:\n") 791 for fname in bogosity: 792 ui.warn(" %s\n" % fname) 793 if not yes_no(ui, "These files will be removed by recommit. Continue?", 794 False): 795 raise util.Abort("recommit would clobber files") 796 797 user = opts['user'] or ui.username() 798 799 message = cmdutil.logmessage(opts) or ui.edit('\n'.join(active.comments()), 800 user) 801 if not message: 802 raise util.Abort('empty commit message') 803 804 name = backup_name(repo.root) 805 bk = CdmBackup(ui, wslist[repo], name) 806 if bk.need_backup(): 807 if yes_no(ui, 'Do you want to backup files first?', True): 808 bk.backup() 809 810 oldtags = repo.tags() 811 clearedtags = [(name, nd, repo.changelog.rev(nd), local) 812 for name, nd, local in active.tags()] 813 814 wslist[repo].squishdeltas(active, message, user=user) 815 816 if clearedtags: 817 ui.write("Removed tags:\n") 818 for name, nd, rev, local in sorted(clearedtags, 819 key=lambda x: x[0].lower()): 820 ui.write(" %5s:%s:\t%s%s\n" % (rev, node.short(nd), 821 name, (local and ' (local)' or ''))) 822 823 for ntag, nnode in sorted(repo.tags().items(), 824 key=lambda x: x[0].lower()): 825 if ntag in oldtags and ntag != "tip": 826 if oldtags[ntag] != nnode: 827 ui.write("tag '%s' now refers to revision %d:%s\n" % 828 (ntag, repo.changelog.rev(nnode), 829 node.short(nnode))) 830 831 832def do_eval(cmd, files, root, changedir=True): 833 if not changedir: 834 os.chdir(root) 835 836 for path in sorted(files): 837 dirn, base = os.path.split(path) 838 839 if changedir: 840 os.chdir(os.path.join(root, dirn)) 841 842 os.putenv('workspace', root) 843 os.putenv('filepath', path) 844 os.putenv('dir', dirn) 845 os.putenv('file', base) 846 os.system(cmd) 847 848 849def cdm_eval(ui, repo, *command, **opts): 850 '''run cmd for each active file 851 852 cmd can refer to: 853 $file - active file basename. 854 $dir - active file dirname. 855 $filepath - path from workspace root to active file. 856 $workspace - full path to workspace root. 857 858 For example "hg eval 'echo $dir; hg log -l3 $file'" will show the last 859 the 3 log entries for each active file, preceded by its directory.''' 860 861 act = wslist[repo].active(opts['parent']) 862 cmd = ' '.join(command) 863 files = [x.name for x in act if not x.is_removed()] 864 865 do_eval(cmd, files, repo.root, not opts['remain']) 866 867 868def cdm_apply(ui, repo, *command, **opts): 869 '''apply cmd to all active files 870 871 For example 'hg apply wc -l' outputs a line count of active files.''' 872 873 act = wslist[repo].active(opts['parent']) 874 875 if opts['remain']: 876 appnd = ' $filepath' 877 else: 878 appnd = ' $file' 879 880 cmd = ' '.join(command) + appnd 881 files = [x.name for x in act if not x.is_removed()] 882 883 do_eval(cmd, files, repo.root, not opts['remain']) 884 885 886def cdm_reparent(ui, repo, parent): 887 '''reparent your workspace 888 889 Updates the 'default' path.''' 890 891 filename = repo.join('hgrc') 892 893 p = ui.expandpath(parent) 894 if not p: 895 raise util.Abort("could not find parent: %s" % parent) 896 897 cp = util.configparser() 898 try: 899 cp.read(filename) 900 except ConfigParser.ParsingError, inst: 901 raise util.Abort('failed to parse %s\n%s' % (filename, inst)) 902 903 try: 904 fh = open(filename, 'w') 905 except IOError, e: 906 raise util.Abort('Failed to open workspace configuration: %s' % e) 907 908 if not cp.has_section('paths'): 909 cp.add_section('paths') 910 cp.set('paths', 'default', p) 911 cp.write(fh) 912 fh.close() 913 914 915def backup_name(fullpath): 916 '''Create a backup directory name based on the specified path. 917 918 In most cases this is the basename of the path specified, but 919 certain cases are handled specially to create meaningful names''' 920 921 special = ['usr/closed'] 922 923 fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep) 924 925 # 926 # If a path is 'special', we append the basename of the path to 927 # the path element preceding the constant, special, part. 928 # 929 # Such that for instance: 930 # /foo/bar/onnv-fixes/usr/closed 931 # has a backup name of: 932 # onnv-fixes-closed 933 # 934 for elt in special: 935 elt = elt.split(os.path.sep) 936 pathpos = len(elt) 937 938 if fullpath[-pathpos:] == elt: 939 return "%s-%s" % (fullpath[-pathpos - 1], elt[-1]) 940 else: 941 return fullpath[-1] 942 943 944def cdm_backup(ui, repo, if_newer=False): 945 '''make backup copies of all workspace changes 946 947 Backups will be stored in ~/cdm.backup/<basename of workspace>.''' 948 949 name = backup_name(repo.root) 950 bk = CdmBackup(ui, wslist[repo], name) 951 952 if if_newer and not bk.need_backup(): 953 ui.status('backup is up-to-date\n') 954 else: 955 bk.backup() 956 957 958def cdm_restore(ui, repo, backup, **opts): 959 '''restore workspace from backup 960 961 Restores a workspace from the specified backup directory and generation 962 (which defaults to the latest).''' 963 964 if not os.getcwd().startswith(repo.root): 965 raise util.Abort('restore is not safe to run with -R') 966 if wslist[repo].modified(): 967 raise util.Abort('Workspace has uncommitted changes') 968 if wslist[repo].merged(): 969 raise util.Abort('Workspace has an uncommitted merge') 970 if wslist[repo].branched(): 971 raise util.Abort('Workspace has an uncommitted branch') 972 973 if opts['generation']: 974 gen = int(opts['generation']) 975 else: 976 gen = None 977 978 if os.path.exists(backup): 979 backup = os.path.abspath(backup) 980 981 bk = CdmBackup(ui, wslist[repo], backup) 982 bk.restore(gen) 983 984 985def cdm_webrev(ui, repo, **opts): 986 '''generate webrev and optionally upload it 987 988 This command passes all arguments to webrev script''' 989 990 webrev_args = "" 991 for key in opts.keys(): 992 if opts[key]: 993 if type(opts[key]) == type(True): 994 webrev_args += '-' + key + ' ' 995 else: 996 webrev_args += '-' + key + ' ' + opts[key] + ' ' 997 998 retval = os.system('webrev ' + webrev_args) 999 if retval != 0: 1000 return retval - 255 1001 1002 return 0 1003 1004 1005cmdtable = { 1006 'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'), 1007 ('r', 'remain', None, 'do not change directories')], 1008 'hg apply [-p PARENT] [-r] command...'), 1009 'arcs': (cdm_arcs, [('p', 'parent', '', 'parent workspace')], 1010 'hg arcs [-p PARENT]'), 1011 '^backup|bu': (cdm_backup, [('t', 'if-newer', None, 1012 'only backup if workspace files are newer')], 1013 'hg backup [-t]'), 1014 'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')], 1015 'hg branchchk [-p PARENT]'), 1016 'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')], 1017 'hg bugs [-p PARENT]'), 1018 'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')], 1019 'hg cddlchk [-p PARENT]'), 1020 'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'), 1021 ('N', 'nocheck', None, 1022 'do not compare comments with databases')], 1023 'hg comchk [-p PARENT]'), 1024 'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')], 1025 'hg comments [-p PARENT]'), 1026 'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')], 1027 'hg copyright [-p PARENT]'), 1028 'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')], 1029 'hg cstyle [-p PARENT]'), 1030 'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'), 1031 ('r', 'remain', None, 'do not change directories')], 1032 'hg eval [-p PARENT] [-r] command...'), 1033 'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')], 1034 'hg hdrchk [-p PARENT]'), 1035 'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')], 1036 'hg jstyle [-p PARENT]'), 1037 'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')], 1038 'hg keywords [-p PARENT]'), 1039 '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'), 1040 ('r', 'removed', None, 'show removed files'), 1041 ('a', 'added', None, 'show added files'), 1042 ('m', 'modified', None, 'show modified files')], 1043 'hg list [-amrRu] [-p PARENT]'), 1044 'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')], 1045 'hg mapfilechk [-p PARENT]'), 1046 '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')], 1047 'hg nits [-p PARENT]'), 1048 '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'), 1049 ('N', 'nocheck', None, 'skip RTI check')], 1050 'hg pbchk [-N] [-p PARENT]'), 1051 'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')], 1052 'hg permchk [-p PARENT]'), 1053 '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace'), 1054 ('a', 'text', None, 'treat all files as text'), 1055 ('g', 'git', None, 'use extended git diff format'), 1056 ('w', 'ignore-all-space', None, 1057 'ignore white space when comparing lines'), 1058 ('b', 'ignore-space-change', None, 1059 'ignore changes in the amount of white space'), 1060 ('B', 'ignore-blank-lines', None, 1061 'ignore changes whos lines are all blank'), 1062 ('U', 'unified', 3, 1063 'number of lines of context to show'), 1064 ('I', 'include', [], 1065 'include names matching the given patterns'), 1066 ('X', 'exclude', [], 1067 'exclude names matching the given patterns')], 1068 'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'), 1069 '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'), 1070 ('f', 'force', None, 'force operation'), 1071 ('m', 'message', '', 1072 'use <text> as commit message'), 1073 ('l', 'logfile', '', 1074 'read commit message from file'), 1075 ('u', 'user', '', 1076 'record user as committer')], 1077 'hg recommit [-f] [-p PARENT]'), 1078 'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')], 1079 'hg renamed [-p PARENT]'), 1080 'reparent': (cdm_reparent, [], 'hg reparent PARENT'), 1081 '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')], 1082 'hg restore [-g GENERATION] BACKUP'), 1083 'rtichk': (cdm_rtichk, [('p', 'parent', '', 'parent workspace'), 1084 ('N', 'nocheck', None, 'skip RTI check')], 1085 'hg rtichk [-N] [-p PARENT]'), 1086 'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')], 1087 'hg tagchk [-p PARENT]'), 1088 'webrev': (cdm_webrev, [('C', 'C', '', 'ITS priority file'), 1089 ('D', 'D', '', 'delete remote webrev'), 1090 ('I', 'I', '', 'ITS configuration file'), 1091 ('i', 'i', '', 'include file'), 1092 ('l', 'l', '', 'extract file list from putback -n'), 1093 ('N', 'N', None, 'supress comments'), 1094 ('n', 'n', None, 'do not generate webrev'), 1095 ('O', 'O', None, 'OpenSolaris mode'), 1096 ('o', 'o', '', 'output directory'), 1097 ('p', 'p', '', 'use specified parent'), 1098 ('t', 't', '', 'upload target'), 1099 ('U', 'U', None, 'upload the webrev'), 1100 ('w', 'w', '', 'use wx active file')], 1101 'hg webrev [WEBREV_OPTIONS]'), 1102} 1103