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 ui.write('Checking for multiple heads (or branches):\n') 389 390 heads = set(repo.heads()) 391 parents = set([x.node() for x in repo.workingctx().parents()]) 392 393 # 394 # We care if there's more than one head, and those heads aren't 395 # identical to the dirstate parents (if they are identical, it's 396 # an uncommitted merge which mergechk will catch, no need to 397 # complain twice). 398 # 399 if len(heads) > 1 and heads != parents: 400 ui.write('Workspace has multiple heads (or branches):\n') 401 for head in [repo.changectx(head) for head in heads]: 402 ui.write(" %d:%s\t%s\n" % 403 (head.rev(), str(head), head.description().splitlines()[0])) 404 ui.write('You must merge and recommit.\n') 405 return 1 406 407 ui.write('\nChecking for branch changes:\n') 408 409 if repo.dirstate.branch() != 'default': 410 ui.write("Warning: Workspace tip has named branch: '%s'\n" 411 "Only gatekeepers should push new branches.\n" 412 "Use the following commands to restore the branch name:\n" 413 " hg branch [-f] default\n" 414 " hg commit\n" 415 "You should also recommit before integration\n" % 416 (repo.dirstate.branch())) 417 return 1 418 419 branches = repo.branchtags().keys() 420 if len(branches) > 1: 421 ui.write('Warning: Workspace has named branches:\n') 422 for t in branches: 423 if t == 'default': 424 continue 425 ui.write("\t%s\n" % t) 426 427 ui.write("Only gatekeepers should push new branches.\n" 428 "Use the following commands to remove extraneous branches.\n" 429 " hg branch [-f] default\n" 430 " hg commit" 431 "You should also recommit before integration\n") 432 return 1 433 434 return 0 435 436 437def cdm_rtichk(ui, repo, **opts): 438 '''check active bug/RFEs for approved RTIs 439 440 Only works on SWAN.''' 441 442 if opts['nocheck']: 443 ui.status('Skipping RTI checks...\n') 444 return 0 445 446 if not onSWAN(): 447 ui.write('RTI checks only work on SWAN, skipping...\n') 448 return 0 449 450 active = opts.get('active') or wslist[repo].active(opts['parent']) 451 452 ui.write('RTI check:\n') 453 454 bugs = [] 455 456 for com in active.comments(): 457 match = Comments.isBug(com) 458 if match and match.group(1) not in bugs: 459 bugs.append(match.group(1)) 460 461 # RTI normalizes the gate path for us 462 return int(not Rti.rti(bugs, gatePath=opts['parent'], output=ui)) 463 464 465def cdm_keywords(ui, repo, **opts): 466 '''check source files do not contain SCCS keywords''' 467 468 active = opts.get('active') or wslist[repo].active(opts['parent']) 469 470 ui.write('Keywords check:\n') 471 472 ret = 0 473 for entry in sorted(active): 474 if entry.is_removed(): 475 continue 476 477 path = wslist[repo].filepath(entry.name) 478 fh = open(path, 'r') 479 ret |= Keywords.keywords(fh, output=ui) 480 fh.close() 481 return ret 482 483 484# 485# NB: 486# There's no reason to hook this up as an invokable command, since 487# we have 'hg status', but it must accept the same arguments. 488# 489def cdm_outchk(ui, repo, **opts): 490 '''Warn the user if they have uncommitted changes''' 491 492 ui.write('Checking for uncommitted changes:\n') 493 494 st = wslist[repo].modified() 495 if st: 496 ui.write('Warning: the following files have uncommitted changes:\n') 497 for elt in st: 498 ui.write(' %s\n' % elt) 499 return 1 500 return 0 501 502 503def cdm_mergechk(ui, repo, **opts): 504 '''Warn the user if their workspace contains merges''' 505 506 active = opts.get('active') or wslist[repo].active(opts['parent']) 507 508 ui.write('Checking for merges:\n') 509 510 merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1], 511 active.revs) 512 513 if merges: 514 ui.write('Workspace contains the following merges:\n') 515 for rev in merges: 516 desc = rev.description().splitlines() 517 ui.write(' %s:%s\t%s\n' % 518 (rev.rev() or "working", str(rev), 519 desc and desc[0] or "*** uncommitted change ***")) 520 return 1 521 return 0 522 523 524def run_checks(ws, cmds, **opts): 525 '''Run CMDS (with OPTS) over active files in WS''' 526 active = ws.active(opts['parent']) 527 528 ret = 0 529 530 for cmd in cmds: 531 name = cmd.func_name.split('_')[1] 532 if not ws.ui.configbool('cdm', name, True): 533 ws.ui.status('Skipping %s check...\n' % name) 534 else: 535 ws.ui.pushbuffer() 536 537 result = cmd(ws.ui, ws.repo, active=active, **opts) 538 ret |= result 539 540 output = ws.ui.popbuffer() 541 if not ws.ui.quiet or result != 0: 542 ws.ui.write(output, '\n') 543 return ret 544 545 546def cdm_nits(ui, repo, **opts): 547 '''check for stylistic nits in active files 548 549 Run cddlchk, copyright, cstyle, hdrchk, jstyle, permchk, and 550 keywords checks.''' 551 552 cmds = [cdm_cddlchk, 553 cdm_copyright, 554 cdm_cstyle, 555 cdm_hdrchk, 556 cdm_jstyle, 557 cdm_permchk, 558 cdm_keywords] 559 560 return run_checks(wslist[repo], cmds, **opts) 561 562 563def cdm_pbchk(ui, repo, **opts): 564 '''pre-putback check all active files 565 566 Run cddlchk, comchk, copyright, cstyle, hdrchk, jstyle, permchk, tagchk, 567 branchchk, keywords and rtichk checks. Additionally, warn about 568 uncommitted changes.''' 569 570 # 571 # The current ordering of these is that the commands from cdm_nits 572 # run first in the same order as they would in cdm_nits. Then the 573 # pbchk specifics run 574 # 575 cmds = [cdm_cddlchk, 576 cdm_copyright, 577 cdm_cstyle, 578 cdm_hdrchk, 579 cdm_jstyle, 580 cdm_permchk, 581 cdm_keywords, 582 cdm_comchk, 583 cdm_tagchk, 584 cdm_branchchk, 585 cdm_rtichk, 586 cdm_outchk, 587 cdm_mergechk] 588 589 return run_checks(wslist[repo], cmds, **opts) 590 591 592def cdm_recommit(ui, repo, **opts): 593 '''compact outgoing deltas into a single, conglomerate delta''' 594 595 if not os.getcwd().startswith(repo.root): 596 raise util.Abort('recommit is not safe to run with -R') 597 598 if wslist[repo].modified(): 599 raise util.Abort('workspace has uncommitted changes') 600 601 if wslist[repo].merged(): 602 raise util.Abort('workspace contains uncommitted merge') 603 604 if wslist[repo].branched(): 605 raise util.Abort('workspace contains uncommitted branch') 606 607 if wslist[repo].mq_applied(): 608 raise util.Abort("workspace has Mq patches applied") 609 610 wlock = repo.wlock() 611 lock = repo.lock() 612 613 heads = repo.heads() 614 if len(heads) > 1: 615 ui.warn('Workspace has multiple heads (or branches):\n') 616 for head in heads: 617 ui.warn('\t%d\n' % repo.changelog.rev(head)) 618 raise util.Abort('you must merge before recommitting') 619 620 active = wslist[repo].active(opts['parent']) 621 622 if len(active.revs) <= 0: 623 raise util.Abort("no changes to recommit") 624 625 if len(active.files()) <= 0: 626 ui.warn("Recommitting %d active changesets, but no active files\n" % 627 len(active.revs)) 628 629 # 630 # During the course of a recommit, any file bearing a name matching the 631 # source name of any renamed file will be clobbered by the operation. 632 # 633 # As such, we ask the user before proceeding. 634 # 635 bogosity = [f.parentname for f in active if f.is_renamed() and 636 os.path.exists(repo.wjoin(f.parentname))] 637 638 if bogosity: 639 ui.warn("The following file names are the original name of a rename " 640 "and also present\n" 641 "in the working directory:\n") 642 for fname in bogosity: 643 ui.warn(" %s\n" % fname) 644 if not yes_no(ui, "These files will be removed by recommit. Continue?", 645 False): 646 raise util.Abort("recommit would clobber files") 647 648 user = opts['user'] or ui.username() 649 650 message = cmdutil.logmessage(opts) or ui.edit('\n'.join(active.comments()), 651 user) 652 if not message: 653 raise util.Abort('empty commit message') 654 655 name = backup_name(repo.root) 656 bk = CdmBackup(ui, wslist[repo], name) 657 if bk.need_backup(): 658 if yes_no(ui, 'Do you want to backup files first?', True): 659 bk.backup() 660 661 oldtags = repo.tags() 662 clearedtags = [(name, nd, repo.changelog.rev(nd), local) 663 for name, nd, local in active.tags()] 664 665 wslist[repo].squishdeltas(active, message, user=user) 666 667 if clearedtags: 668 ui.write("Removed tags:\n") 669 for name, nd, rev, local in clearedtags: 670 ui.write(" %s %s:%s%s\n" % (name, rev, node.short(nd), 671 (local and ' (local)') or '')) 672 673 for ntag, nnode in repo.tags().items(): 674 if ntag in oldtags and ntag != "tip": 675 if oldtags[ntag] != nnode: 676 ui.write("tag %s now refers to revision %d:%s\n" % 677 (ntag, repo.changelog.rev(nnode), node.short(nnode))) 678 679 680def do_eval(cmd, files, root, changedir=True): 681 if not changedir: 682 os.chdir(root) 683 684 for path in sorted(files): 685 dirn, base = os.path.split(path) 686 687 if changedir: 688 os.chdir(os.path.join(root, dirn)) 689 690 os.putenv('workspace', root) 691 os.putenv('filepath', path) 692 os.putenv('dir', dirn) 693 os.putenv('file', base) 694 os.system(cmd) 695 696 697def cdm_eval(ui, repo, *command, **opts): 698 '''run cmd for each active file 699 700 cmd can refer to: 701 $file - active file basename. 702 $dir - active file dirname. 703 $filepath - path from workspace root to active file. 704 $workspace - full path to workspace root. 705 706 For example "hg eval 'echo $dir; hg log -l3 $file'" will show the last 707 the 3 log entries for each active file, preceded by its directory.''' 708 709 act = wslist[repo].active(opts['parent']) 710 cmd = ' '.join(command) 711 files = [x.name for x in act if not x.is_removed()] 712 713 do_eval(cmd, files, repo.root, not opts['remain']) 714 715 716def cdm_apply(ui, repo, *command, **opts): 717 '''apply cmd to all active files 718 719 For example 'hg apply wc -l' outputs a line count of active files.''' 720 721 act = wslist[repo].active(opts['parent']) 722 723 if opts['remain']: 724 appnd = ' $filepath' 725 else: 726 appnd = ' $file' 727 728 cmd = ' '.join(command) + appnd 729 files = [x.name for x in act if not x.is_removed()] 730 731 do_eval(cmd, files, repo.root, not opts['remain']) 732 733 734def cdm_reparent(ui, repo, parent): 735 '''reparent your workspace 736 737 Updates the 'default' path.''' 738 739 filename = repo.join('hgrc') 740 741 cp = util.configparser() 742 try: 743 cp.read(filename) 744 except ConfigParser.ParsingError, inst: 745 raise util.Abort('failed to parse %s\n%s' % (filename, inst)) 746 747 try: 748 fh = open(filename, 'w') 749 except IOError, e: 750 raise util.Abort('Failed to open workspace configuration: %s' % e) 751 752 if not cp.has_section('paths'): 753 cp.add_section('paths') 754 cp.set('paths', 'default', parent) 755 cp.write(fh) 756 fh.close() 757 758 759def backup_name(fullpath): 760 '''Create a backup directory name based on the specified path. 761 762 In most cases this is the basename of the path specified, but 763 certain cases are handled specially to create meaningful names''' 764 765 special = ['usr/closed'] 766 767 fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep) 768 769 # 770 # If a path is 'special', we append the basename of the path to 771 # the path element preceding the constant, special, part. 772 # 773 # Such that for instance: 774 # /foo/bar/onnv-fixes/usr/closed 775 # has a backup name of: 776 # onnv-fixes-closed 777 # 778 for elt in special: 779 elt = elt.split(os.path.sep) 780 pathpos = len(elt) 781 782 if fullpath[-pathpos:] == elt: 783 return "%s-%s" % (fullpath[-pathpos - 1], elt[-1]) 784 else: 785 return fullpath[-1] 786 787 788def cdm_backup(ui, repo, if_newer=False): 789 '''make backup copies of all workspace changes 790 791 Backups will be stored in ~/cdm.backup/<basename of workspace>.''' 792 793 name = backup_name(repo.root) 794 bk = CdmBackup(ui, wslist[repo], name) 795 796 if if_newer and not bk.need_backup(): 797 ui.status('backup is up-to-date\n') 798 else: 799 bk.backup() 800 801 802def cdm_restore(ui, repo, backup, **opts): 803 '''restore workspace from backup 804 805 Restores a workspace from the specified backup directory and generation 806 (which defaults to the latest).''' 807 808 if not os.getcwd().startswith(repo.root): 809 raise util.Abort('restore is not safe to run with -R') 810 if wslist[repo].modified(): 811 raise util.Abort('Workspace has uncommitted changes') 812 if wslist[repo].merged(): 813 raise util.Abort('Workspace has an uncommitted merge') 814 if wslist[repo].branched(): 815 raise util.Abort('Workspace has an uncommitted branch') 816 817 if opts['generation']: 818 gen = int(opts['generation']) 819 else: 820 gen = None 821 822 if os.path.exists(backup): 823 backup = os.path.abspath(backup) 824 825 bk = CdmBackup(ui, wslist[repo], backup) 826 bk.restore(gen) 827 828cmdtable = { 829 'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'), 830 ('r', 'remain', None, 'do not change directories')], 831 'hg apply [-p PARENT] [-r] command...'), 832 'arcs': (cdm_arcs, [('p', 'parent', '', 'parent workspace')], 833 'hg arcs [-p PARENT]'), 834 '^backup|bu': (cdm_backup, [('t', 'if-newer', None, 835 'only backup if workspace files are newer')], 836 'hg backup [-t]'), 837 'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')], 838 'hg branchchk [-p PARENT]'), 839 'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')], 840 'hg bugs [-p PARENT]'), 841 'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')], 842 'hg cddlchk [-p PARENT]'), 843 'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'), 844 ('N', 'nocheck', None, 845 'do not compare comments with databases')], 846 'hg comchk [-p PARENT]'), 847 'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')], 848 'hg comments [-p PARENT]'), 849 'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')], 850 'hg copyright [-p PARENT]'), 851 'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')], 852 'hg cstyle [-p PARENT]'), 853 'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'), 854 ('r', 'remain', None, 'do not change directories')], 855 'hg eval [-p PARENT] [-r] command...'), 856 'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')], 857 'hg hdrchk [-p PARENT]'), 858 'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')], 859 'hg jstyle [-p PARENT]'), 860 'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')], 861 'hg keywords [-p PARENT]'), 862 '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'), 863 ('r', 'removed', None, 'show removed files'), 864 ('a', 'added', None, 'show added files'), 865 ('m', 'modified', None, 'show modified files')], 866 'hg list [-amrRu] [-p PARENT]'), 867 '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')], 868 'hg nits [-p PARENT]'), 869 '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'), 870 ('N', 'nocheck', None, 'skip RTI check')], 871 'hg pbchk [-N] [-p PARENT]'), 872 'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')], 873 'hg permchk [-p PARENT]'), 874 '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace')], 875 'hg pdiffs [-p PARENT]'), 876 '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'), 877 ('f', 'force', None, 'force operation'), 878 ('m', 'message', '', 879 'use <text> as commit message'), 880 ('l', 'logfile', '', 881 'read commit message from file'), 882 ('u', 'user', '', 883 'record user as committer')], 884 'hg recommit [-f] [-p PARENT]'), 885 'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')], 886 'hg renamed [-p PARENT]'), 887 'reparent': (cdm_reparent, [], 'hg reparent PARENT'), 888 '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')], 889 'hg restore [-g GENERATION] BACKUP'), 890 'rtichk': (cdm_rtichk, [('p', 'parent', '', 'parent workspace'), 891 ('N', 'nocheck', None, 'skip RTI check')], 892 'hg rtichk [-N] [-p PARENT]'), 893 'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')], 894 'hg tagchk [-p PARENT]'), 895} 896