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 2008 Sun Microsystems, Inc. All rights reserved. 18# Use is subject to license terms. 19# 20# ident "%Z%%M% %I% %E% SMI" 21# 22 23'''workspace extensions for mercurial 24 25This extension contains a number of commands to help you work within 26the OpenSolaris consolidations. 27 28Common uses: 29 30Show diffs relative to parent workspace - pdiffs 31Check source style rules - nits 32Run pre-putback checks - pbchk 33Collapse all your changes into a single changeset - recommit''' 34 35 36# 37# NB: This assumes the normal directory structure, with this 38# extension 2 levels below .../lib/python. 39# 40# If you change that, change this 41# 42import sys, os 43sys.path.insert(1, "%s/../../" % os.path.dirname(__file__)) 44 45from onbld.Scm import Version 46from mercurial import util 47 48try: 49 Version.check_version() 50except Version.VersionMismatch, badversion: 51 raise util.Abort("Version Mismatch:\n %s\n" % badversion) 52 53import ConfigParser 54from mercurial import cmdutil, node 55 56from onbld.Scm.WorkSpace import WorkSpace, ActiveEntry 57from onbld.Scm.Backup import CdmBackup 58from onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk 59from onbld.Checks import JStyle, Keywords, Rti, onSWAN 60 61 62def yes_no(ui, msg, default): 63 if default: 64 prompt = ' [Y/n]:' 65 defanswer = 'y' 66 else: 67 prompt = ' [y/N]:' 68 defanswer = 'n' 69 70 if ui.interactive and sys.stdin.isatty(): 71 resp = ui.prompt(msg + prompt, r'([Yy(es)?|[Nn]o?)?', 72 default=defanswer) 73 if not resp: 74 return default 75 elif resp[0] in ['Y', 'y']: 76 return True 77 else: 78 return False 79 else: 80 return default 81 82# 83# Adding a reference to WorkSpace from a repo causes a circular reference 84# repo <-> WorkSpace. 85# 86# This prevents repo, WorkSpace and members thereof from being garbage 87# collected. Since transactions are aborted when the transaction object 88# is collected, and localrepo holds a reference to the most recently created 89# transaction, this prevents transactions from cleanly aborting. 90# 91# Instead, we hold the repo->WorkSpace association in a dictionary, breaking 92# that dependence. 93# 94wslist = {} 95 96 97def reposetup(ui, repo): 98 if repo.local() and repo not in wslist: 99 wslist[repo] = WorkSpace(repo) 100 101 102def cdm_pdiffs(ui, repo, parent=None): 103 '''list workspace diffs relative to parent workspace 104 105 The parent tip is taken to be the latest revision shared between 106 us and the parent workspace.''' 107 diffs = wslist[repo].pdiff(parent) 108 if diffs: 109 ui.write(diffs) 110 111 112def cdm_list(ui, repo, **opts): 113 '''list files changed relative to parent workspace 114 115 The parent tip is taken to be the latest revision shared between 116 us and the parent workspace.''' 117 118 wanted = [] 119 120 if opts['added']: 121 wanted.append(ActiveEntry.ADDED) 122 if opts['modified']: 123 wanted.append(ActiveEntry.MODIFIED) 124 if opts['removed']: 125 wanted.append(ActiveEntry.REMOVED) 126 127 act = wslist[repo].active(opts['parent']) 128 chngmap = {ActiveEntry.MODIFIED: 'modified', 129 ActiveEntry.ADDED: 'added', 130 ActiveEntry.REMOVED: 'removed'} 131 132 lst = {} 133 for entry in act: 134 if wanted and (entry.change not in wanted): 135 continue 136 137 chngstr = chngmap[entry.change] 138 if chngstr not in lst: 139 lst[chngstr] = [] 140 lst[chngstr].append(entry) 141 142 for chng in sorted(lst.keys()): 143 ui.write(chng + ':\n') 144 for elt in sorted(lst[chng]): 145 if elt.is_renamed(): 146 ui.write('\t%s (renamed from %s)\n' % (elt.name, 147 elt.parentname)) 148 elif elt.is_copied(): 149 ui.write('\t%s (copied from %s)\n' % (elt.name, 150 elt.parentname)) 151 else: 152 ui.write('\t%s\n' % elt.name) 153 154 155def cdm_arcs(ui, repo, parent=None): 156 'show all ARC cases in checkin comments' 157 act = wslist[repo].active(parent) 158 159 # We take a set of the appropriate comments to eliminate duplicates. 160 for elt in set(filter(Comments.isARC, act.comments())): 161 ui.write(elt + '\n') 162 163 164def cdm_bugs(ui, repo, parent=None): 165 'show all bug IDs in checkin comments' 166 act = wslist[repo].active(parent) 167 168 for elt in set(filter(Comments.isBug, act.comments())): 169 ui.write(elt + '\n') 170 171 172def cdm_comments(ui, repo, parent=None): 173 'show checkin comments for active files' 174 act = wslist[repo].active(parent) 175 176 for elt in act.comments(): 177 ui.write(elt + '\n') 178 179 180def cdm_renamed(ui, repo, parent=None): 181 '''show renamed active files 182 183 Renamed files are shown in the format 184 185 newname oldname 186 187 One pair per-line.''' 188 189 act = wslist[repo].active(parent) 190 191 for entry in sorted(filter(lambda x: x.is_renamed(), act)): 192 ui.write('%s %s\n' % (entry.name, entry.parentname)) 193 194 195def cdm_comchk(ui, repo, **opts): 196 '''check checkin comments for active files 197 198 Check that checkin comments conform to O/N rules.''' 199 200 active = opts.get('active') or wslist[repo].active(opts['parent']) 201 202 ui.write('Comments check:\n') 203 204 check_db = not opts['nocheck'] 205 return Comments.comchk(active.comments(), check_db=check_db, output=ui) 206 207 208def cdm_cddlchk(ui, repo, **opts): 209 '''check for a valid CDDL block in active files 210 211 See http://www.opensolaris.org/os/community/on/devref_toc/devref_7/#7_2_3_nonformatting_considerations 212 for more info.''' 213 214 active = opts.get('active') or wslist[repo].active(opts['parent']) 215 216 ui.write('CDDL block check:\n') 217 218 lenient = True 219 ret = 0 220 221 for entry in sorted(active): 222 if entry.is_removed(): 223 continue 224 elif entry.is_added(): 225 lenient = False 226 else: 227 lenient = True 228 229 path = wslist[repo].filepath(entry.name) 230 fh = open(path, 'r') 231 ret |= Cddl.cddlchk(fh, lenient=lenient, output=ui) 232 fh.close() 233 return ret 234 235 236def cdm_copyright(ui, repo, **opts): 237 '''check active files for valid copyrights 238 239 Check that all active files have a valid copyright containing the 240 current year (and *only* the current year). 241 See http://www.opensolaris.org/os/project/muskoka/on_dev/golden_rules.txt 242 for more info.''' 243 244 active = opts.get('active') or wslist[repo].active(opts['parent']) 245 246 ui.write('Copyright check:\n') 247 248 ret = 0 249 250 for entry in sorted(active): 251 if entry.is_removed(): 252 continue 253 254 path = wslist[repo].filepath(entry.name) 255 256 fh = open(path, 'r') 257 ret |= Copyright.copyright(fh, output=ui) 258 fh.close() 259 return ret 260 261 262def cdm_hdrchk(ui, repo, **opts): 263 '''check active header files conform to O/N rules''' 264 265 active = opts.get('active') or wslist[repo].active(opts['parent']) 266 267 ui.write('Header format check:\n') 268 269 ret = 0 270 271 for entry in sorted(active): 272 if entry.is_removed(): 273 continue 274 275 path = wslist[repo].filepath(entry.name) 276 277 if entry.name.endswith('.h'): 278 fh = open(path, 'r') 279 ret |= HdrChk.hdrchk(fh, lenient=True, output=ui) 280 fh.close() 281 return ret 282 283 284def cdm_cstyle(ui, repo, **opts): 285 '''check active C source files conform to the C Style Guide 286 287 See http://opensolaris.org/os/community/documentation/getting_started_docs/cstyle.ms.pdf''' 288 289 active = opts.get('active') or wslist[repo].active(opts['parent']) 290 291 ui.write('C style check:\n') 292 293 ret = 0 294 295 for entry in sorted(active): 296 if entry.is_removed(): 297 continue 298 299 path = wslist[repo].filepath(entry.name) 300 301 if entry.name.endswith('.c') or entry.name.endswith('.h'): 302 fh = open(path, 'r') 303 ret |= CStyle.cstyle(fh, output=ui, 304 picky=True, check_posix_types=True, 305 check_continuation=True) 306 fh.close() 307 return ret 308 309 310def cdm_jstyle(ui, repo, **opts): 311 'check active Java source files for common stylistic errors' 312 313 active = opts.get('active') or wslist[repo].active(opts['parent']) 314 315 ui.write('Java style check:\n') 316 317 ret = 0 318 319 for entry in sorted(active): 320 if entry.is_removed(): 321 continue 322 323 path = wslist[repo].filepath(entry.name) 324 325 if entry.name.endswith('.java'): 326 fh = open(path, 'r') 327 ret |= JStyle.jstyle(fh, output=ui, picky=True) 328 fh.close() 329 return ret 330 331 332def cdm_permchk(ui, repo, **opts): 333 '''check active files permission - warn +x (execute) mode''' 334 335 active = opts.get('active') or wslist[repo].active(opts['parent']) 336 337 ui.write('File permission check:\n') 338 339 exeFiles = [] 340 for entry in sorted(active): 341 if entry.is_removed(): 342 continue 343 344 path = wslist[repo].filepath(entry.name) 345 346 if active.localtip.manifest().execf(path): 347 exeFiles.append(path) 348 349 if len(exeFiles) > 0: 350 ui.write('Warning: the following active file(s) have executable mode ' 351 '(+x) permission set,\nremove unless intentional:\n') 352 for fname in exeFiles: 353 ui.write(" %s\n" % fname) 354 355 return len(exeFiles) > 0 356 357 358def cdm_tagchk(ui, repo, **opts): 359 '''check if .hgtags is active and issue warning 360 361 Tag sharing among repositories is restricted to gatekeepers''' 362 363 active = opts.get('active') or wslist[repo].active(opts['parent']) 364 365 ui.write('Checking for new tags:\n') 366 367 if ".hgtags" in active: 368 tfile = wslist[repo].filepath('.hgtags') 369 ptip = active.parenttip.rev() 370 371 ui.write('Warning: Workspace contains new non-local tags.\n' 372 'Only gatekeepers should add or modify such tags.\n' 373 'Use the following commands to revert these changes:\n' 374 ' hg revert -r%d %s\n' 375 ' hg commit %s\n' 376 'You should also recommit before integration\n' % 377 (ptip, tfile, tfile)) 378 379 return 1 380 381 return 0 382 383 384def cdm_branchchk(ui, repo, **opts): 385 '''check if multiple heads (or branches) are present, or if 386 branch changes are made''' 387 388 active = opts.get('active') or wslist[repo].active(opts['parent']) 389 390 ui.write('Checking for multiple heads (or branches):\n') 391 392 heads = set(repo.heads()) 393 parents = set([x.node() for x in repo.workingctx().parents()]) 394 395 # 396 # We care if there's more than one head, and those heads aren't 397 # identical to the dirstate parents (if they are identical, it's 398 # an uncommitted merge which mergechk will catch, no need to 399 # complain twice). 400 # 401 if len(heads) > 1 and heads != parents: 402 ui.write('Workspace has multiple heads (or branches):\n') 403 for head in [repo.changectx(head) for head in heads]: 404 ui.write(" %d:%s\t%s\n" % 405 (head.rev(), str(head), head.description().splitlines()[0])) 406 ui.write('You must merge and recommit.\n') 407 return 1 408 409 ui.write('\nChecking for branch changes:\n') 410 411 if active.localtip.branch() != 'default': 412 ui.write("Warning: Workspace tip has named branch: '%s'\n" 413 "Only gatekeepers should push new branches.\n" 414 "Use the following commands to restore the branch name:\n" 415 " hg branch [-f] default\n" 416 " hg commit\n" 417 "You should also recommit before integration\n" % 418 (active.localtip.branch())) 419 return 1 420 421 branches = repo.branchtags().keys() 422 if len(branches) > 1: 423 ui.write('Warning: Workspace has named branches:\n') 424 for t in branches: 425 if t == 'default': 426 continue 427 ui.write("\t%s\n" % t) 428 429 ui.write("Only gatekeepers should push new branches.\n" 430 "Use the following commands to remove extraneous branches.\n" 431 " hg branch [-f] default\n" 432 " hg commit" 433 "You should also recommit before integration\n") 434 return 1 435 436 return 0 437 438 439def cdm_rtichk(ui, repo, **opts): 440 '''check active bug/RFEs for approved RTIs 441 442 Only works on SWAN.''' 443 444 if opts['nocheck']: 445 ui.status('Skipping RTI checks...\n') 446 return 0 447 448 if not onSWAN(): 449 ui.write('RTI checks only work on SWAN, skipping...\n') 450 return 0 451 452 active = opts.get('active') or wslist[repo].active(opts['parent']) 453 454 ui.write('RTI check:\n') 455 456 bugs = [] 457 458 for com in active.comments(): 459 match = Comments.isBug(com) 460 if match and match.group(1) not in bugs: 461 bugs.append(match.group(1)) 462 463 # RTI normalizes the gate path for us 464 return int(not Rti.rti(bugs, gatePath=opts['parent'], output=ui)) 465 466 467def cdm_keywords(ui, repo, **opts): 468 '''check source files do not contain SCCS keywords''' 469 470 active = opts.get('active') or wslist[repo].active(opts['parent']) 471 472 ui.write('Keywords check:\n') 473 474 ret = 0 475 for entry in sorted(active): 476 if entry.is_removed(): 477 continue 478 479 path = wslist[repo].filepath(entry.name) 480 fh = open(path, 'r') 481 ret |= Keywords.keywords(fh, output=ui) 482 fh.close() 483 return ret 484 485 486# 487# NB: 488# There's no reason to hook this up as an invokable command, since 489# we have 'hg status', but it must accept the same arguments. 490# 491def cdm_outchk(ui, repo, **opts): 492 '''Warn the user if they have uncommitted changes''' 493 494 ui.write('Checking for uncommitted changes:\n') 495 496 st = wslist[repo].modified() 497 if st: 498 ui.write('Warning: the following files have uncommitted changes:\n') 499 for elt in st: 500 ui.write(' %s\n' % elt) 501 return 1 502 return 0 503 504 505def cdm_mergechk(ui, repo, **opts): 506 '''Warn the user if their workspace contains merges''' 507 508 active = opts.get('active') or wslist[repo].active(opts['parent']) 509 510 ui.write('Checking for merges:\n') 511 512 merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1], 513 active.revs) 514 515 if merges: 516 ui.write('Workspace contains the following merges:\n') 517 for rev in merges: 518 desc = rev.description().splitlines() 519 ui.write(' %s:%s\t%s\n' % 520 (rev.rev() or "working", str(rev), 521 desc and desc[0] or "*** uncommitted change ***")) 522 return 1 523 return 0 524 525 526def run_checks(ws, cmds, **opts): 527 '''Run CMDS (with OPTS) over active files in WS''' 528 active = ws.active(opts['parent']) 529 530 ret = 0 531 532 for cmd in cmds: 533 name = cmd.func_name.split('_')[1] 534 if not ws.ui.configbool('cdm', name, True): 535 ws.ui.status('Skipping %s check...\n' % name) 536 else: 537 ws.ui.pushbuffer() 538 539 result = cmd(ws.ui, ws.repo, active=active, **opts) 540 ret |= result 541 542 output = ws.ui.popbuffer() 543 if not ws.ui.quiet or result != 0: 544 ws.ui.write(output, '\n') 545 return ret 546 547 548def cdm_nits(ui, repo, **opts): 549 '''check for stylistic nits in active files 550 551 Run cddlchk, copyright, cstyle, hdrchk, jstyle, permchk, and 552 keywords checks.''' 553 554 cmds = [cdm_cddlchk, 555 cdm_copyright, 556 cdm_cstyle, 557 cdm_hdrchk, 558 cdm_jstyle, 559 cdm_permchk, 560 cdm_keywords] 561 562 return run_checks(wslist[repo], cmds, **opts) 563 564 565def cdm_pbchk(ui, repo, **opts): 566 '''pre-putback check all active files 567 568 Run cddlchk, comchk, copyright, cstyle, hdrchk, jstyle, permchk, tagchk, 569 branchchk, keywords and rtichk checks. Additionally, warn about 570 uncommitted changes.''' 571 572 # 573 # The current ordering of these is that the commands from cdm_nits 574 # run first in the same order as they would in cdm_nits. Then the 575 # pbchk specifics run 576 # 577 cmds = [cdm_cddlchk, 578 cdm_copyright, 579 cdm_cstyle, 580 cdm_hdrchk, 581 cdm_jstyle, 582 cdm_permchk, 583 cdm_keywords, 584 cdm_comchk, 585 cdm_tagchk, 586 cdm_branchchk, 587 cdm_rtichk, 588 cdm_outchk, 589 cdm_mergechk] 590 591 return run_checks(wslist[repo], cmds, **opts) 592 593 594def cdm_recommit(ui, repo, **opts): 595 '''compact outgoing deltas into a single, conglomerate delta''' 596 597 if not os.getcwd().startswith(repo.root): 598 raise util.Abort('recommit is not safe to run with -R') 599 600 if wslist[repo].modified(): 601 raise util.Abort('workspace has uncommitted changes') 602 603 if wslist[repo].merged(): 604 raise util.Abort('workspace contains uncommitted merge') 605 606 if wslist[repo].branched(): 607 raise util.Abort('workspace contains uncommitted branch') 608 609 if wslist[repo].mq_applied(): 610 raise util.Abort("workspace has Mq patches applied") 611 612 wlock = repo.wlock() 613 lock = repo.lock() 614 615 heads = repo.heads() 616 if len(heads) > 1: 617 ui.warn('Workspace has multiple heads (or branches):\n') 618 for head in heads: 619 ui.warn('\t%d\n' % repo.changelog.rev(head)) 620 raise util.Abort('you must merge before recommitting') 621 622 active = wslist[repo].active(opts['parent']) 623 624 if len(active.revs) <= 0: 625 raise util.Abort("no changes to recommit") 626 627 if len(active.files()) <= 0: 628 ui.warn("Recommitting %d active changesets, but no active files\n" % 629 len(active.revs)) 630 631 # 632 # During the course of a recommit, any file bearing a name matching the 633 # source name of any renamed file will be clobbered by the operation. 634 # 635 # As such, we ask the user before proceeding. 636 # 637 bogosity = [f.parentname for f in active if f.is_renamed() and 638 os.path.exists(repo.wjoin(f.parentname))] 639 640 if bogosity: 641 ui.warn("The following file names are the original name of a rename " 642 "and also present\n" 643 "in the working directory:\n") 644 for fname in bogosity: 645 ui.warn(" %s\n" % fname) 646 if not yes_no(ui, "These files will be removed by recommit. Continue?", 647 False): 648 raise util.Abort("recommit would clobber files") 649 650 user = opts['user'] or ui.username() 651 652 message = cmdutil.logmessage(opts) or ui.edit('\n'.join(active.comments()), 653 user) 654 if not message: 655 raise util.Abort('empty commit message') 656 657 name = backup_name(repo.root) 658 bk = CdmBackup(ui, wslist[repo], name) 659 if bk.need_backup(): 660 if yes_no(ui, 'Do you want to backup files first?', True): 661 bk.backup() 662 663 oldtags = repo.tags() 664 clearedtags = [(name, nd, repo.changelog.rev(nd), local) 665 for name, nd, local in active.tags()] 666 667 wslist[repo].squishdeltas(active, message, user=user) 668 669 if clearedtags: 670 ui.write("Removed tags:\n") 671 for name, nd, rev, local in clearedtags: 672 ui.write(" %s %s:%s%s\n" % (name, rev, node.short(nd), 673 (local and ' (local)') or '')) 674 675 for ntag, nnode in repo.tags().items(): 676 if ntag in oldtags and ntag != "tip": 677 if oldtags[ntag] != nnode: 678 ui.write("tag %s now refers to revision %d:%s\n" % 679 (ntag, repo.changelog.rev(nnode), node.short(nnode))) 680 681 682def do_eval(cmd, files, root, changedir=True): 683 if not changedir: 684 os.chdir(root) 685 686 for path in sorted(files): 687 dirn, base = os.path.split(path) 688 689 if changedir: 690 os.chdir(os.path.join(root, dirn)) 691 692 os.putenv('workspace', root) 693 os.putenv('filepath', path) 694 os.putenv('dir', dirn) 695 os.putenv('file', base) 696 os.system(cmd) 697 698 699def cdm_eval(ui, repo, *command, **opts): 700 '''run cmd for each active file 701 702 cmd can refer to: 703 $file - active file basename. 704 $dir - active file dirname. 705 $filepath - path from workspace root to active file. 706 $workspace - full path to workspace root. 707 708 For example "hg eval 'echo $dir; hg log -l3 $file'" will show the last 709 the 3 log entries for each active file, preceded by its directory.''' 710 711 act = wslist[repo].active(opts['parent']) 712 cmd = ' '.join(command) 713 files = [x.name for x in act if not x.is_removed()] 714 715 do_eval(cmd, files, repo.root, not opts['remain']) 716 717 718def cdm_apply(ui, repo, *command, **opts): 719 '''apply cmd to all active files 720 721 For example 'hg apply wc -l' outputs a line count of active files.''' 722 723 act = wslist[repo].active(opts['parent']) 724 725 if opts['remain']: 726 appnd = ' $filepath' 727 else: 728 appnd = ' $file' 729 730 cmd = ' '.join(command) + appnd 731 files = [x.name for x in act if not x.is_removed()] 732 733 do_eval(cmd, files, repo.root, not opts['remain']) 734 735 736def cdm_reparent(ui, repo, parent): 737 '''reparent your workspace 738 739 Updates the 'default' path.''' 740 741 filename = repo.join('hgrc') 742 743 cp = util.configparser() 744 try: 745 cp.read(filename) 746 except ConfigParser.ParsingError, inst: 747 raise util.Abort('failed to parse %s\n%s' % (filename, inst)) 748 749 try: 750 fh = open(filename, 'w') 751 except IOError, e: 752 raise util.Abort('Failed to open workspace configuration: %s' % e) 753 754 if not cp.has_section('paths'): 755 cp.add_section('paths') 756 cp.set('paths', 'default', parent) 757 cp.write(fh) 758 fh.close() 759 760 761def backup_name(fullpath): 762 '''Create a backup directory name based on the specified path. 763 764 In most cases this is the basename of the path specified, but 765 certain cases are handled specially to create meaningful names''' 766 767 special = ['usr/closed'] 768 769 fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep) 770 771 # 772 # If a path is 'special', we append the basename of the path to 773 # the path element preceding the constant, special, part. 774 # 775 # Such that for instance: 776 # /foo/bar/onnv-fixes/usr/closed 777 # has a backup name of: 778 # onnv-fixes-closed 779 # 780 for elt in special: 781 elt = elt.split(os.path.sep) 782 pathpos = len(elt) 783 784 if fullpath[-pathpos:] == elt: 785 return "%s-%s" % (fullpath[-pathpos - 1], elt[-1]) 786 else: 787 return fullpath[-1] 788 789 790def cdm_backup(ui, repo, if_newer=False): 791 '''make backup copies of all workspace changes 792 793 Backups will be stored in ~/cdm.backup/<basename of workspace>.''' 794 795 name = backup_name(repo.root) 796 bk = CdmBackup(ui, wslist[repo], name) 797 798 if if_newer and not bk.need_backup(): 799 ui.status('backup is up-to-date\n') 800 else: 801 bk.backup() 802 803 804def cdm_restore(ui, repo, backup, **opts): 805 '''restore workspace from backup 806 807 Restores a workspace from the specified backup directory and generation 808 (which defaults to the latest).''' 809 810 if not os.getcwd().startswith(repo.root): 811 raise util.Abort('restore is not safe to run with -R') 812 if wslist[repo].modified(): 813 raise util.Abort('Workspace has uncommitted changes') 814 if wslist[repo].merged(): 815 raise util.Abort('Workspace has an uncommitted merge') 816 if wslist[repo].branched(): 817 raise util.Abort('Workspace has an uncommitted branch') 818 819 if opts['generation']: 820 gen = int(opts['generation']) 821 else: 822 gen = None 823 824 if os.path.exists(backup): 825 backup = os.path.abspath(backup) 826 827 bk = CdmBackup(ui, wslist[repo], backup) 828 bk.restore(gen) 829 830cmdtable = { 831 'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'), 832 ('r', 'remain', None, 'do not change directories')], 833 'hg apply [-p PARENT] [-r] command...'), 834 'arcs': (cdm_arcs, [('p', 'parent', '', 'parent workspace')], 835 'hg arcs [-p PARENT]'), 836 '^backup|bu': (cdm_backup, [('t', 'if-newer', None, 837 'only backup if workspace files are newer')], 838 'hg backup [-t]'), 839 'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')], 840 'hg branchchk [-p PARENT]'), 841 'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')], 842 'hg bugs [-p PARENT]'), 843 'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')], 844 'hg cddlchk [-p PARENT]'), 845 'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'), 846 ('N', 'nocheck', None, 847 'do not compare comments with databases')], 848 'hg comchk [-p PARENT]'), 849 'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')], 850 'hg comments [-p PARENT]'), 851 'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')], 852 'hg copyright [-p PARENT]'), 853 'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')], 854 'hg cstyle [-p PARENT]'), 855 'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'), 856 ('r', 'remain', None, 'do not change directories')], 857 'hg eval [-p PARENT] [-r] command...'), 858 'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')], 859 'hg hdrchk [-p PARENT]'), 860 'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')], 861 'hg jstyle [-p PARENT]'), 862 'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')], 863 'hg keywords [-p PARENT]'), 864 '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'), 865 ('r', 'removed', None, 'show removed files'), 866 ('a', 'added', None, 'show added files'), 867 ('m', 'modified', None, 'show modified files')], 868 'hg list [-amrRu] [-p PARENT]'), 869 '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')], 870 'hg nits [-p PARENT]'), 871 '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'), 872 ('N', 'nocheck', None, 'skip RTI check')], 873 'hg pbchk [-N] [-p PARENT]'), 874 'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')], 875 'hg permchk [-p PARENT]'), 876 '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace')], 877 'hg pdiffs [-p PARENT]'), 878 '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'), 879 ('f', 'force', None, 'force operation'), 880 ('m', 'message', '', 881 'use <text> as commit message'), 882 ('l', 'logfile', '', 883 'read commit message from file'), 884 ('u', 'user', '', 885 'record user as committer')], 886 'hg recommit [-f] [-p PARENT]'), 887 'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')], 888 'hg renamed [-p PARENT]'), 889 'reparent': (cdm_reparent, [], 'hg reparent PARENT'), 890 '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')], 891 'hg restore [-g GENERATION] BACKUP'), 892 'rtichk': (cdm_rtichk, [('p', 'parent', '', 'parent workspace'), 893 ('N', 'nocheck', None, 'skip RTI check')], 894 'hg rtichk [-N] [-p PARENT]'), 895 'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')], 896 'hg tagchk [-p PARENT]'), 897} 898