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