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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 18 # Copyright 2008, 2011 Richard Lowe 19 # Copyright (c) 2013, Joyent Inc. All rights reserved. 20 # 21 22 '''OpenSolaris extensions to Mercurial 23 24 This extension contains a number of commands to help you work with 25 the OpenSolaris consolidations. It provides commands to check your 26 changes against the various style rules used for OpenSolaris, to 27 backup and restore your changes, to generate code reviews, and to 28 prepare your changes for integration. 29 30 31 The Parent 32 33 To provide a uniform notion of parent workspace regardless of 34 filesystem-based access, Cadmium uses the highest numbered changeset 35 on the current branch that is also in the parent workspace to 36 represent the parent workspace. 37 38 39 The Active List 40 41 Many Cadmium commands operate on the active list, the set of 42 files ('active files') you have changed in this workspace in relation 43 to its parent workspace, and the metadata (commentary, primarily) 44 associated with those changes. 45 46 47 NOT Files 48 49 Many of Cadmium's commands to check that your work obeys the 50 various stylistic rules of the OpenSolaris consolidations (such as 51 those run by 'hg nits') allow files to be excluded from this checking 52 by means of NOT files kept in the .hg/cdm/ directory of the Mercurial 53 repository for one-time exceptions, and in the exception_lists 54 directory at the repository root for permanent exceptions. (For ON, 55 these would mean one in $CODEMGR_WS and one in 56 $CODEMGR_WS/usr/closed). 57 58 These files are in the same format as the Mercurial hgignore 59 file, a description of which is available in the hgignore(5) manual 60 page. 61 62 63 Common Tasks 64 65 - Show diffs relative to parent workspace - pdiffs 66 - Check source style rules - nits 67 - Run pre-integration checks - pbchk 68 - Collapse all your changes into a single changeset - recommit 69 ''' 70 71 import atexit, os, re, sys, stat, termios 72 73 74 # 75 # Adjust the load path based on the location of cdm.py and the version 76 # of python into which it is being loaded. This assumes the normal 77 # onbld directory structure, where cdm.py is in 78 # lib/python(version)?/onbld/hgext/. If that changes so too must 79 # this. 80 # 81 # This and the case below are not equivalent. In this case we may be 82 # loading a cdm.py in python2.X/ via the lib/python/ symlink but need 83 # python2.Y in sys.path. 84 # 85 sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "..", "..", 86 "python%d.%d" % sys.version_info[:2])) 87 88 # 89 # Add the relative path from cdm.py to usr/src/tools to the load path, 90 # such that a cdm.py loaded from the source tree uses the modules also 91 # within the source tree. 92 # 93 sys.path.insert(2, os.path.join(os.path.dirname(__file__), "..", "..")) 94 95 from onbld.Scm import Version 96 from onbld.Scm import Ignore 97 from mercurial import util 98 99 try: 100 Version.check_version() 101 except Version.VersionMismatch, badversion: 102 raise util.Abort("Version Mismatch:\n %s\n" % badversion) 103 104 from mercurial import cmdutil, node, patch 105 106 from onbld.Scm.WorkSpace import WorkSpace, WorkList 107 from onbld.Scm.Backup import CdmBackup 108 from onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk 109 from onbld.Checks import JStyle, Keywords, Mapfile 110 111 112 def yes_no(ui, msg, default): 113 if default: 114 prompt = ' [Y/n]:' 115 defanswer = 'y' 116 else: 117 prompt = ' [y/N]:' 118 defanswer = 'n' 119 120 if Version.at_least("1.4"): 121 index = ui.promptchoice(msg + prompt, ['&yes', '&no'], 122 default=['y', 'n'].index(defanswer)) 123 resp = ('y', 'n')[index] 124 else: 125 resp = ui.prompt(msg + prompt, ['&yes', '&no'], default=defanswer) 126 127 return resp[0] in ('Y', 'y') 128 129 130 def buildfilelist(ws, parent, files): 131 '''Build a list of files in which we're interested. 132 133 If no files are specified take files from the active list relative 134 to 'parent'. 135 136 Return a list of 2-tuples the first element being a path relative 137 to the current directory and the second an entry from the active 138 list, or None if an explicit file list was given.''' 139 140 if files: 141 return [(path, None) for path in sorted(files)] 142 else: 143 active = ws.active(parent=parent) 144 return [(ws.filepath(e.name), e) for e in sorted(active)] 145 buildfilelist = util.cachefunc(buildfilelist) 146 147 148 def not_check(repo, cmd): 149 '''return a function which returns boolean indicating whether a file 150 should be skipped for CMD.''' 151 152 # 153 # The ignore routines need a canonical path to the file (relative to the 154 # repo root), whereas the check commands get paths relative to the cwd. 155 # 156 # Wrap our argument such that the path is canonified before it is checked. 157 # 158 def canonified_check(ignfunc): 159 def f(path): 160 cpath = util.canonpath(repo.root, repo.getcwd(), path) 161 return ignfunc(cpath) 162 return f 163 164 ignorefiles = [] 165 166 for f in [repo.join('cdm/%s.NOT' % cmd), 167 repo.wjoin('exception_lists/%s' % cmd)]: 168 if os.path.exists(f): 169 ignorefiles.append(f) 170 171 if ignorefiles: 172 ign = Ignore.ignore(repo.root, ignorefiles) 173 return canonified_check(ign) 174 else: 175 return util.never 176 177 178 def abort_if_dirty(ws): 179 '''Abort if the workspace has uncommitted changes, merges, 180 branches, or has Mq patches applied''' 181 182 if ws.modified(): 183 raise util.Abort('workspace has uncommitted changes') 184 if ws.merged(): 185 raise util.Abort('workspace contains uncommitted merge') 186 if ws.branched(): 187 raise util.Abort('workspace contains uncommitted branch') 188 if ws.mq_applied(): 189 raise util.Abort('workspace has Mq patches applied') 190 191 192 # 193 # Adding a reference to WorkSpace from a repo causes a circular reference 194 # repo <-> WorkSpace. 195 # 196 # This prevents repo, WorkSpace and members thereof from being garbage 197 # collected. Since transactions are aborted when the transaction object 198 # is collected, and localrepo holds a reference to the most recently created 199 # transaction, this prevents transactions from cleanly aborting. 200 # 201 # Instead, we hold the repo->WorkSpace association in a dictionary, breaking 202 # that dependence. 203 # 204 wslist = {} 205 206 207 def reposetup(ui, repo): 208 if repo.local() and repo not in wslist: 209 wslist[repo] = WorkSpace(repo) 210 211 if ui.interactive() and sys.stdin.isatty(): 212 ui.setconfig('hooks', 'preoutgoing.cdm_pbconfirm', 213 'python:hgext_cdm.pbconfirm') 214 215 216 def pbconfirm(ui, repo, hooktype, source): 217 def wrapper(settings=None): 218 termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings) 219 220 if source == 'push': 221 if not yes_no(ui, "Are you sure you wish to push?", False): 222 return 1 223 else: 224 settings = termios.tcgetattr(sys.stdin.fileno()) 225 orig = list(settings) 226 atexit.register(wrapper, orig) 227 settings[3] = settings[3] & (~termios.ISIG) # c_lflag 228 termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings) 229 230 231 def cdm_pdiffs(ui, repo, *pats, **opts): 232 '''diff workspace against its parent 233 234 Show differences between this workspace and its parent workspace 235 in the same manner as 'hg diff'. 236 237 For a description of the changeset used to represent the parent 238 workspace, see The Parent in the extension documentation ('hg help 239 cdm'). 240 ''' 241 242 act = wslist[repo].active(opts.get('parent')) 243 if not act.revs: 244 return 245 246 # 247 # If no patterns were specified, either explicitly or via -I or -X 248 # use the active list files to avoid a workspace walk. 249 # 250 if pats or opts.get('include') or opts.get('exclude'): 251 matchfunc = wslist[repo].matcher(pats=pats, opts=opts) 252 else: 253 matchfunc = wslist[repo].matcher(files=act.files()) 254 255 opts = patch.diffopts(ui, opts) 256 diffs = wslist[repo].diff(act.parenttip.node(), act.localtip.node(), 257 match=matchfunc, opts=opts) 258 if diffs: 259 ui.write(diffs) 260 261 262 def cdm_list(ui, repo, **opts): 263 '''list active files (those changed in this workspace) 264 265 Display a list of files changed in this workspace as compared to 266 its parent workspace. 267 268 File names are displayed one-per line, grouped by manner in which 269 they changed (added, modified, removed). Information about 270 renames or copies is output in parentheses following the file 271 name. 272 273 For a description of the changeset used to represent the parent 274 workspace, see The Parent in the extension documentation ('hg help 275 cdm'). 276 277 Output can be filtered by change type with --added, --modified, 278 and --removed. By default, all files are shown. 279 ''' 280 281 act = wslist[repo].active(opts['parent']) 282 wanted = set(x for x in ('added', 'modified', 'removed') if opts[x]) 283 changes = {} 284 285 for entry in act: 286 if wanted and (entry.change not in wanted): 287 continue 288 289 if entry.change not in changes: 290 changes[entry.change] = [] 291 changes[entry.change].append(entry) 292 293 for change in sorted(changes.keys()): 294 ui.write(change + ':\n') 295 296 for entry in sorted(changes[change]): 297 if entry.is_renamed(): 298 ui.write('\t%s (renamed from %s)\n' % (entry.name, 299 entry.parentname)) 300 elif entry.is_copied(): 301 ui.write('\t%s (copied from %s)\n' % (entry.name, 302 entry.parentname)) 303 else: 304 ui.write('\t%s\n' % entry.name) 305 306 307 def cdm_bugs(ui, repo, parent=None): 308 '''show all bug IDs referenced in changeset comments''' 309 310 act = wslist[repo].active(parent) 311 312 for elt in set(filter(Comments.isBug, act.comments())): 313 ui.write(elt + '\n') 314 315 316 def cdm_comments(ui, repo, parent=None): 317 '''show changeset commentary for all active changesets''' 318 act = wslist[repo].active(parent) 319 320 for elt in act.comments(): 321 ui.write(elt + '\n') 322 323 324 def cdm_renamed(ui, repo, parent=None): 325 '''show renamed active files 326 327 Renamed files are shown in the format:: 328 329 new-name old-name 330 331 One pair per-line. 332 ''' 333 334 act = wslist[repo].active(parent) 335 336 for entry in sorted(filter(lambda x: x.is_renamed(), act)): 337 ui.write('%s %s\n' % (entry.name, entry.parentname)) 338 339 340 def cdm_comchk(ui, repo, **opts): 341 '''check active changeset comment formatting 342 343 Check that active changeset comments conform to O/N rules. 344 345 Each comment line must contain either one bug or ARC case ID 346 followed by its synopsis, or credit an external contributor. 347 ''' 348 349 active = wslist[repo].active(opts.get('parent')) 350 351 ui.write('Comments check:\n') 352 353 check_db = not opts.get('nocheck') 354 return Comments.comchk(active.comments(), check_db=check_db, output=ui) 355 356 357 def cdm_cddlchk(ui, repo, *args, **opts): 358 '''check for a valid CDDL header comment in all active files. 359 360 Check active files for a valid Common Development and Distribution 361 License (CDDL) block comment. 362 363 Newly added files are checked for a copy of the CDDL header 364 comment. Modified files are only checked if they contain what 365 appears to be an existing CDDL header comment. 366 367 Files can be excluded from this check using the cddlchk.NOT file. 368 See NOT Files in the extension documentation ('hg help cdm'). 369 ''' 370 371 filelist = buildfilelist(wslist[repo], opts.get('parent'), args) 372 exclude = not_check(repo, 'cddlchk') 373 lenient = True 374 ret = 0 375 376 ui.write('CDDL block check:\n') 377 378 for f, e in filelist: 379 if e and e.is_removed(): 380 continue 381 elif (e or opts.get('honour_nots')) and exclude(f): 382 ui.status('Skipping %s...\n' % f) 383 continue 384 elif e and e.is_added(): 385 lenient = False 386 else: 387 lenient = True 388 389 fh = open(f, 'r') 390 ret |= Cddl.cddlchk(fh, lenient=lenient, output=ui) 391 fh.close() 392 return ret 393 394 395 def cdm_mapfilechk(ui, repo, *args, **opts): 396 '''check for a valid mapfile header block in active files 397 398 Check that all link-editor mapfiles contain the standard mapfile 399 header comment directing the reader to the document containing 400 Solaris object versioning rules (README.mapfile). 401 402 Files can be excluded from this check using the mapfilechk.NOT 403 file. See NOT Files in the extension documentation ('hg help 404 cdm'). 405 ''' 406 407 filelist = buildfilelist(wslist[repo], opts.get('parent'), args) 408 exclude = not_check(repo, 'mapfilechk') 409 ret = 0 410 411 # We are interested in examining any file that has the following 412 # in its final path segment: 413 # - Contains the word 'mapfile' 414 # - Begins with 'map.' 415 # - Ends with '.map' 416 # We don't want to match unless these things occur in final path segment 417 # because directory names with these strings don't indicate a mapfile. 418 # We also ignore files with suffixes that tell us that the files 419 # are not mapfiles. 420 MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$', 421 re.IGNORECASE) 422 NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE) 423 424 ui.write('Mapfile comment check:\n') 425 426 for f, e in filelist: 427 if e and e.is_removed(): 428 continue 429 elif (not MapfileRE.match(f)) or NotMapSuffixRE.match(f): 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 |= Mapfile.mapfilechk(fh, output=ui) 437 fh.close() 438 return ret 439 440 441 def cdm_copyright(ui, repo, *args, **opts): 442 '''check each active file for a current and correct copyright notice 443 444 Check that all active files have a correctly formed copyright 445 notice containing the current year. 446 447 See the Non-Formatting Considerations section of the OpenSolaris 448 Developer's Reference for more info on the correct form of 449 copyright notice. 450 (http://hub.opensolaris.org/bin/view/Community+Group+on/devref_7#H723NonFormattingConsiderations) 451 452 Files can be excluded from this check using the copyright.NOT file. 453 See NOT Files in the extension documentation ('hg help cdm'). 454 ''' 455 456 filelist = buildfilelist(wslist[repo], opts.get('parent'), args) 457 exclude = not_check(repo, 'copyright') 458 ret = 0 459 460 ui.write('Copyright check:\n') 461 462 for f, e in filelist: 463 if e and e.is_removed(): 464 continue 465 elif (e or opts.get('honour_nots')) and exclude(f): 466 ui.status('Skipping %s...\n' % f) 467 continue 468 469 fh = open(f, 'r') 470 ret |= Copyright.copyright(fh, output=ui) 471 fh.close() 472 return ret 473 474 475 def cdm_hdrchk(ui, repo, *args, **opts): 476 '''check active C header files conform to the O/N header rules 477 478 Check that any added or modified C header files conform to the O/N 479 header rules. 480 481 See the section 'HEADER STANDARDS' in the hdrchk(1) manual page 482 for more information on the rules for O/N header file formatting. 483 484 Files can be excluded from this check using the hdrchk.NOT file. 485 See NOT Files in the extension documentation ('hg help cdm'). 486 ''' 487 488 filelist = buildfilelist(wslist[repo], opts.get('parent'), args) 489 exclude = not_check(repo, 'hdrchk') 490 ret = 0 491 492 ui.write('Header format check:\n') 493 494 for f, e in filelist: 495 if e and e.is_removed(): 496 continue 497 elif not f.endswith('.h'): 498 continue 499 elif (e or opts.get('honour_nots')) and exclude(f): 500 ui.status('Skipping %s...\n' % f) 501 continue 502 503 fh = open(f, 'r') 504 ret |= HdrChk.hdrchk(fh, lenient=True, output=ui) 505 fh.close() 506 return ret 507 508 509 def cdm_cstyle(ui, repo, *args, **opts): 510 '''check active C source files conform to the C Style Guide 511 512 Check that any added or modified C source file conform to the C 513 Style Guide. 514 515 See the C Style Guide for more information about correct C source 516 formatting. 517 (http://hub.opensolaris.org/bin/download/Community+Group+on/WebHome/cstyle.ms.pdf) 518 519 Files can be excluded from this check using the cstyle.NOT file. 520 See NOT Files in the extension documentation ('hg help cdm'). 521 ''' 522 523 filelist = buildfilelist(wslist[repo], opts.get('parent'), args) 524 exclude = not_check(repo, 'cstyle') 525 ret = 0 526 527 ui.write('C style check:\n') 528 529 for f, e in filelist: 530 if e and e.is_removed(): 531 continue 532 elif not (f.endswith('.c') or f.endswith('.h')): 533 continue 534 elif (e or opts.get('honour_nots')) and exclude(f): 535 ui.status('Skipping %s...\n' % f) 536 continue 537 538 fh = open(f, 'r') 539 ret |= CStyle.cstyle(fh, output=ui, 540 picky=True, check_posix_types=True, 541 check_continuation=True) 542 fh.close() 543 return ret 544 545 546 def cdm_jstyle(ui, repo, *args, **opts): 547 '''check active Java source files for common stylistic errors 548 549 Files can be excluded from this check using the jstyle.NOT file. 550 See NOT Files in the extension documentation ('hg help cdm'). 551 ''' 552 553 filelist = buildfilelist(wslist[repo], opts.get('parent'), args) 554 exclude = not_check(repo, 'jstyle') 555 ret = 0 556 557 ui.write('Java style check:\n') 558 559 for f, e in filelist: 560 if e and e.is_removed(): 561 continue 562 elif not f.endswith('.java'): 563 continue 564 elif (e or opts.get('honour_nots')) and exclude(f): 565 ui.status('Skipping %s...\n' % f) 566 continue 567 568 fh = open(f, 'r') 569 ret |= JStyle.jstyle(fh, output=ui, picky=True) 570 fh.close() 571 return ret 572 573 574 def cdm_permchk(ui, repo, *args, **opts): 575 '''check the permissions of each active file 576 577 Check that the file permissions of each added or modified file do not 578 contain the executable bit. 579 580 Files can be excluded from this check using the permchk.NOT file. 581 See NOT Files in the extension documentation ('hg help cdm'). 582 ''' 583 584 filelist = buildfilelist(wslist[repo], opts.get('parent'), args) 585 exclude = not_check(repo, 'permchk') 586 exeFiles = [] 587 588 ui.write('File permission check:\n') 589 590 for f, e in filelist: 591 if e and e.is_removed(): 592 continue 593 elif (e or opts.get('honour_nots')) and exclude(f): 594 ui.status('Skipping %s...\n' % f) 595 continue 596 597 mode = stat.S_IMODE(os.stat(f)[stat.ST_MODE]) 598 if mode & stat.S_IEXEC: 599 exeFiles.append(f) 600 601 if len(exeFiles) > 0: 602 ui.write('Warning: the following active file(s) have executable mode ' 603 '(+x) permission set,\nremove unless intentional:\n') 604 for fname in exeFiles: 605 ui.write(" %s\n" % fname) 606 607 return len(exeFiles) > 0 608 609 610 def cdm_tagchk(ui, repo, **opts): 611 '''check modification of workspace tags 612 613 Check for any modification of the repository's .hgtags file. 614 615 With the exception of the gatekeepers, nobody should introduce or 616 modify a repository's tags. 617 ''' 618 619 active = wslist[repo].active(opts.get('parent')) 620 621 ui.write('Checking for new tags:\n') 622 623 if ".hgtags" in active: 624 tfile = wslist[repo].filepath('.hgtags') 625 ptip = active.parenttip.rev() 626 627 ui.write('Warning: Workspace contains new non-local tags.\n' 628 'Only gatekeepers should add or modify such tags.\n' 629 'Use the following commands to revert these changes:\n' 630 ' hg revert -r%d %s\n' 631 ' hg commit %s\n' 632 'You should also recommit before integration\n' % 633 (ptip, tfile, tfile)) 634 635 return 1 636 637 return 0 638 639 640 def cdm_branchchk(ui, repo, **opts): 641 '''check for changes in number or name of branches 642 643 Check that the workspace contains only a single head, that it is 644 on the branch 'default', and that no new branches have been 645 introduced. 646 ''' 647 648 ui.write('Checking for multiple heads (or branches):\n') 649 650 heads = set(repo.heads()) 651 parents = set([x.node() for x in wslist[repo].workingctx().parents()]) 652 653 # 654 # We care if there's more than one head, and those heads aren't 655 # identical to the dirstate parents (if they are identical, it's 656 # an uncommitted merge which mergechk will catch, no need to 657 # complain twice). 658 # 659 if len(heads) > 1 and heads != parents: 660 ui.write('Workspace has multiple heads (or branches):\n') 661 for head in [repo.changectx(head) for head in heads]: 662 ui.write(" %d:%s\t%s\n" % 663 (head.rev(), str(head), head.description().splitlines()[0])) 664 ui.write('You must merge and recommit.\n') 665 return 1 666 667 ui.write('\nChecking for branch changes:\n') 668 669 if repo.dirstate.branch() != 'default': 670 ui.write("Warning: Workspace tip has named branch: '%s'\n" 671 "Only gatekeepers should push new branches.\n" 672 "Use the following commands to restore the branch name:\n" 673 " hg branch [-f] default\n" 674 " hg commit\n" 675 "You should also recommit before integration\n" % 676 (repo.dirstate.branch())) 677 return 1 678 679 branches = repo.branchtags().keys() 680 if len(branches) > 1: 681 ui.write('Warning: Workspace has named branches:\n') 682 for t in branches: 683 if t == 'default': 684 continue 685 ui.write("\t%s\n" % t) 686 687 ui.write("Only gatekeepers should push new branches.\n" 688 "Use the following commands to remove extraneous branches.\n" 689 " hg branch [-f] default\n" 690 " hg commit" 691 "You should also recommit before integration\n") 692 return 1 693 694 return 0 695 696 697 def cdm_keywords(ui, repo, *args, **opts): 698 '''check active files for SCCS keywords 699 700 Check that any added or modified files do not contain SCCS keywords 701 (#ident lines, etc.). 702 703 Files can be excluded from this check using the keywords.NOT file. 704 See NOT Files in the extension documentation ('hg help cdm'). 705 ''' 706 707 filelist = buildfilelist(wslist[repo], opts.get('parent'), args) 708 exclude = not_check(repo, 'keywords') 709 ret = 0 710 711 ui.write('Keywords check:\n') 712 713 for f, e in filelist: 714 if e and e.is_removed(): 715 continue 716 elif (e or opts.get('honour_nots')) and exclude(f): 717 ui.status('Skipping %s...\n' % f) 718 continue 719 720 fh = open(f, 'r') 721 ret |= Keywords.keywords(fh, output=ui) 722 fh.close() 723 return ret 724 725 726 # 727 # NB: 728 # There's no reason to hook this up as an invokable command, since 729 # we have 'hg status', but it must accept the same arguments. 730 # 731 def cdm_outchk(ui, repo, **opts): 732 '''Warn the user if they have uncommitted changes''' 733 734 ui.write('Checking for uncommitted changes:\n') 735 736 st = wslist[repo].modified() 737 if st: 738 ui.write('Warning: the following files have uncommitted changes:\n') 739 for elt in st: 740 ui.write(' %s\n' % elt) 741 return 1 742 return 0 743 744 745 def cdm_mergechk(ui, repo, **opts): 746 '''Warn the user if their workspace contains merges''' 747 748 active = wslist[repo].active(opts.get('parent')) 749 750 ui.write('Checking for merges:\n') 751 752 merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1], 753 active.revs) 754 755 if merges: 756 ui.write('Workspace contains the following merges:\n') 757 for rev in merges: 758 desc = rev.description().splitlines() 759 ui.write(' %s:%s\t%s\n' % 760 (rev.rev() or "working", str(rev), 761 desc and desc[0] or "*** uncommitted change ***")) 762 return 1 763 return 0 764 765 766 def run_checks(ws, cmds, *args, **opts): 767 '''Run CMDS (with OPTS) over active files in WS''' 768 769 ret = 0 770 771 for cmd in cmds: 772 name = cmd.func_name.split('_')[1] 773 if not ws.ui.configbool('cdm', name, True): 774 ws.ui.status('Skipping %s check...\n' % name) 775 else: 776 ws.ui.pushbuffer() 777 result = cmd(ws.ui, ws.repo, honour_nots=True, *args, **opts) 778 output = ws.ui.popbuffer() 779 780 ret |= result 781 782 if not ws.ui.quiet or result != 0: 783 ws.ui.write(output, '\n') 784 return ret 785 786 787 def cdm_nits(ui, repo, *args, **opts): 788 '''check for stylistic nits in active files 789 790 Check each active file for basic stylistic errors. 791 792 The following checks are run over each active file (see 'hg help 793 <check>' for more information about each): 794 795 - copyright (copyright statements) 796 - cstyle (C source style) 797 - hdrchk (C header style) 798 - jstyle (java source style) 799 - mapfilechk (link-editor mapfiles) 800 - permchk (file permissions) 801 - keywords (SCCS keywords) 802 803 With the global -q/--quiet option, only provide output for those 804 checks which fail. 805 ''' 806 807 cmds = [cdm_copyright, 808 cdm_cstyle, 809 cdm_hdrchk, 810 cdm_jstyle, 811 cdm_mapfilechk, 812 cdm_permchk, 813 cdm_keywords] 814 815 return run_checks(wslist[repo], cmds, *args, **opts) 816 817 818 def cdm_pbchk(ui, repo, **opts): 819 '''run pre-integration checks on this workspace 820 821 Check this workspace for common errors prior to integration. 822 823 The following checks are run over the active list (see 'hg help 824 <check>' for more information about each): 825 826 - branchchk (addition/modification of branches) 827 - comchk (changeset descriptions) 828 - copyright (copyright statements) 829 - cstyle (C source style) 830 - hdrchk (C header style) 831 - jstyle (java source style) 832 - keywords (SCCS keywords) 833 - mapfilechk (link-editor mapfiles) 834 - permchk (file permissions) 835 - tagchk (addition/modification of tags) 836 837 Additionally, the workspace is checked for outgoing merges (which 838 should be removed with 'hg recommit'), and uncommitted changes. 839 840 With the global -q/--quiet option, only provide output for those 841 checks which fail. 842 ''' 843 844 # 845 # The current ordering of these is that the commands from cdm_nits 846 # run first in the same order as they would in cdm_nits, then the 847 # pbchk specifics are run. 848 # 849 cmds = [cdm_copyright, 850 cdm_cstyle, 851 cdm_hdrchk, 852 cdm_jstyle, 853 cdm_mapfilechk, 854 cdm_permchk, 855 cdm_keywords, 856 cdm_comchk, 857 cdm_tagchk, 858 cdm_branchchk, 859 cdm_outchk, 860 cdm_mergechk] 861 862 return run_checks(wslist[repo], cmds, **opts) 863 864 865 def cdm_recommit(ui, repo, **opts): 866 '''replace outgoing changesets with a single equivalent changeset 867 868 Replace all outgoing changesets with a single changeset containing 869 equivalent changes. This removes uninteresting changesets created 870 during development that would only serve as noise in the gate. 871 872 Any changed file that is now identical in content to that in the 873 parent workspace (whether identical in history or otherwise) will 874 not be included in the new changeset. Any merges information will 875 also be removed. 876 877 If no files are changed in comparison to the parent workspace, the 878 outgoing changesets will be removed, but no new changeset created. 879 880 recommit will refuse to run if the workspace contains more than 881 one outgoing head, even if those heads are on the same branch. To 882 recommit with only one branch containing outgoing changesets, your 883 workspace must be on that branch and at that branch head. 884 885 recommit will prompt you to take a backup if your workspace has 886 been changed since the last backup was taken. In almost all 887 cases, you should allow it to take one (the default). 888 889 recommit cannot be run if the workspace contains any uncommitted 890 changes, applied Mq patches, or has multiple outgoing heads (or 891 branches). 892 ''' 893 894 ws = wslist[repo] 895 896 if not os.getcwd().startswith(repo.root): 897 raise util.Abort('recommit is not safe to run with -R') 898 899 abort_if_dirty(ws) 900 901 wlock = repo.wlock() 902 lock = repo.lock() 903 904 try: 905 parent = ws.parent(opts['parent']) 906 between = repo.changelog.nodesbetween(ws.findoutgoing(parent))[2] 907 heads = set(between) & set(repo.heads()) 908 909 if len(heads) > 1: 910 ui.warn('Workspace has multiple outgoing heads (or branches):\n') 911 for head in sorted(map(repo.changelog.rev, heads), reverse=True): 912 ui.warn('\t%d\n' % head) 913 raise util.Abort('you must merge before recommitting') 914 915 # 916 # We can safely use the worklist here, as we know (from the 917 # abort_if_dirty() check above) that the working copy has not been 918 # modified. 919 # 920 active = ws.active(parent) 921 922 if filter(lambda b: len(b.parents()) > 1, active.bases()): 923 raise util.Abort('Cannot recommit a merge of two non-outgoing ' 924 'changesets') 925 926 if len(active.revs) <= 0: 927 raise util.Abort("no changes to recommit") 928 929 if len(active.files()) <= 0: 930 ui.warn("Recommitting %d active changesets, but no active files\n" % 931 len(active.revs)) 932 933 # 934 # During the course of a recommit, any file bearing a name 935 # matching the source name of any renamed file will be 936 # clobbered by the operation. 937 # 938 # As such, we ask the user before proceeding. 939 # 940 bogosity = [f.parentname for f in active if f.is_renamed() and 941 os.path.exists(repo.wjoin(f.parentname))] 942 if bogosity: 943 ui.warn("The following file names are the original name of a " 944 "rename and also present\n" 945 "in the working directory:\n") 946 947 for fname in bogosity: 948 ui.warn(" %s\n" % fname) 949 950 if not yes_no(ui, "These files will be removed by recommit." 951 " Continue?", 952 False): 953 raise util.Abort("recommit would clobber files") 954 955 user = opts['user'] or ui.username() 956 comments = '\n'.join(active.comments()) 957 958 message = cmdutil.logmessage(opts) or ui.edit(comments, user) 959 if not message: 960 raise util.Abort('empty commit message') 961 962 bk = CdmBackup(ui, ws, backup_name(repo.root)) 963 if bk.need_backup(): 964 if yes_no(ui, 'Do you want to backup files first?', True): 965 bk.backup() 966 967 oldtags = repo.tags() 968 clearedtags = [(name, nd, repo.changelog.rev(nd), local) 969 for name, nd, local in active.tags()] 970 971 ws.squishdeltas(active, message, user=user) 972 finally: 973 lock.release() 974 wlock.release() 975 976 if clearedtags: 977 ui.write("Removed tags:\n") 978 for name, nd, rev, local in sorted(clearedtags, 979 key=lambda x: x[0].lower()): 980 ui.write(" %5s:%s:\t%s%s\n" % (rev, node.short(nd), 981 name, (local and ' (local)' or ''))) 982 983 for ntag, nnode in sorted(repo.tags().items(), 984 key=lambda x: x[0].lower()): 985 if ntag in oldtags and ntag != "tip": 986 if oldtags[ntag] != nnode: 987 ui.write("tag '%s' now refers to revision %d:%s\n" % 988 (ntag, repo.changelog.rev(nnode), 989 node.short(nnode))) 990 991 992 def do_eval(cmd, files, root, changedir=True): 993 if not changedir: 994 os.chdir(root) 995 996 for path in sorted(files): 997 dirn, base = os.path.split(path) 998 999 if changedir: 1000 os.chdir(os.path.join(root, dirn)) 1001 1002 os.putenv('workspace', root) 1003 os.putenv('filepath', path) 1004 os.putenv('dir', dirn) 1005 os.putenv('file', base) 1006 os.system(cmd) 1007 1008 1009 def cdm_eval(ui, repo, *command, **opts): 1010 '''run specified command for each active file 1011 1012 Run the command specified on the command line for each active 1013 file, with the following variables present in the environment: 1014 1015 :$file: - active file basename. 1016 :$dir: - active file dirname. 1017 :$filepath: - path from workspace root to active file. 1018 :$workspace: - full path to workspace root. 1019 1020 For example: 1021 1022 hg eval 'echo $dir; hg log -l3 $file' 1023 1024 will show the last the 3 log entries for each active file, 1025 preceded by its directory. 1026 ''' 1027 1028 act = wslist[repo].active(opts['parent']) 1029 cmd = ' '.join(command) 1030 files = [x.name for x in act if not x.is_removed()] 1031 1032 do_eval(cmd, files, repo.root, not opts['remain']) 1033 1034 1035 def cdm_apply(ui, repo, *command, **opts): 1036 '''apply specified command to all active files 1037 1038 Run the command specified on the command line over each active 1039 file. 1040 1041 For example 'hg apply "wc -l"' will output a count of the lines in 1042 each active file. 1043 ''' 1044 1045 act = wslist[repo].active(opts['parent']) 1046 1047 if opts['remain']: 1048 appnd = ' $filepath' 1049 else: 1050 appnd = ' $file' 1051 1052 cmd = ' '.join(command) + appnd 1053 files = [x.name for x in act if not x.is_removed()] 1054 1055 do_eval(cmd, files, repo.root, not opts['remain']) 1056 1057 1058 def cdm_reparent(ui, repo, parent): 1059 '''reparent your workspace 1060 1061 Update the 'default' path alias that is used as the default source 1062 for 'hg pull' and the default destination for 'hg push' (unless 1063 there is a 'default-push' alias). This is also the path all 1064 Cadmium commands treat as your parent workspace. 1065 ''' 1066 1067 def append_new_parent(parent): 1068 fp = None 1069 try: 1070 fp = repo.opener('hgrc', 'a', atomictemp=True) 1071 if fp.tell() != 0: 1072 fp.write('\n') 1073 fp.write('[paths]\n' 1074 'default = %s\n\n' % parent) 1075 fp.rename() 1076 finally: 1077 if fp and not fp.closed: 1078 fp.close() 1079 1080 def update_parent(path, line, parent): 1081 line = line - 1 # The line number we're passed will be 1-based 1082 fp = None 1083 1084 try: 1085 fp = open(path) 1086 data = fp.readlines() 1087 finally: 1088 if fp and not fp.closed: 1089 fp.close() 1090 1091 # 1092 # line will be the last line of any continued block, go back 1093 # to the first removing the continuation as we go. 1094 # 1095 while data[line][0].isspace(): 1096 data.pop(line) 1097 line -= 1 1098 1099 assert data[line].startswith('default') 1100 1101 data[line] = "default = %s\n" % parent 1102 if data[-1] != '\n': 1103 data.append('\n') 1104 1105 try: 1106 fp = util.atomictempfile(path, 'w', 0644) 1107 fp.writelines(data) 1108 fp.rename() 1109 finally: 1110 if fp and not fp.closed: 1111 fp.close() 1112 1113 from mercurial import config 1114 parent = ui.expandpath(parent) 1115 1116 if not os.path.exists(repo.join('hgrc')): 1117 append_new_parent(parent) 1118 return 1119 1120 cfg = config.config() 1121 cfg.read(repo.join('hgrc')) 1122 source = cfg.source('paths', 'default') 1123 1124 if not source: 1125 append_new_parent(parent) 1126 return 1127 else: 1128 path, target = source.rsplit(':', 1) 1129 1130 if path != repo.join('hgrc'): 1131 raise util.Abort("Cannot edit path specification not in repo hgrc\n" 1132 "default path is from: %s" % source) 1133 1134 update_parent(path, int(target), parent) 1135 1136 1137 def backup_name(fullpath): 1138 '''Create a backup directory name based on the specified path. 1139 1140 In most cases this is the basename of the path specified, but 1141 certain cases are handled specially to create meaningful names''' 1142 1143 special = ['usr/closed'] 1144 1145 fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep) 1146 1147 # 1148 # If a path is 'special', we append the basename of the path to 1149 # the path element preceding the constant, special, part. 1150 # 1151 # Such that for instance: 1152 # /foo/bar/onnv-fixes/usr/closed 1153 # has a backup name of: 1154 # onnv-fixes-closed 1155 # 1156 for elt in special: 1157 elt = elt.split(os.path.sep) 1158 pathpos = len(elt) 1159 1160 if fullpath[-pathpos:] == elt: 1161 return "%s-%s" % (fullpath[-pathpos - 1], elt[-1]) 1162 else: 1163 return fullpath[-1] 1164 1165 1166 def cdm_backup(ui, repo, if_newer=False): 1167 '''backup workspace changes and metadata 1168 1169 Create a backup copy of changes made in this workspace as compared 1170 to its parent workspace, as well as important metadata of this 1171 workspace. 1172 1173 NOTE: Only changes as compared to the parent workspace are backed 1174 up. If you lose this workspace and its parent, you will not be 1175 able to restore a backup into a clone of the grandparent 1176 workspace. 1177 1178 By default, backups are stored in the cdm.backup/ directory in 1179 your home directory. This is configurable using the cdm.backupdir 1180 configuration variable, for example: 1181 1182 hg backup --config cdm.backupdir=/net/foo/backups 1183 1184 or place the following in an appropriate hgrc file:: 1185 1186 [cdm] 1187 backupdir = /net/foo/backups 1188 1189 Backups have the same name as the workspace in which they were 1190 taken, with '-closed' appended in the case of O/N's usr/closed. 1191 ''' 1192 1193 name = backup_name(repo.root) 1194 bk = CdmBackup(ui, wslist[repo], name) 1195 1196 wlock = repo.wlock() 1197 lock = repo.lock() 1198 1199 try: 1200 if if_newer and not bk.need_backup(): 1201 ui.status('backup is up-to-date\n') 1202 else: 1203 bk.backup() 1204 finally: 1205 lock.release() 1206 wlock.release() 1207 1208 1209 def cdm_restore(ui, repo, backup, **opts): 1210 '''restore workspace from backup 1211 1212 Restore this workspace from a backup (taken by 'hg backup'). 1213 1214 If the specified backup directory does not exist, it is assumed to 1215 be relative to the cadmium backup directory (~/cdm.backup/ by 1216 default). 1217 1218 For example:: 1219 1220 % hg restore on-rfe - Restore the latest backup of ~/cdm.backup/on-rfe 1221 % hg restore -g3 on-rfe - Restore the 3rd backup of ~/cdm.backup/on-rfe 1222 % hg restore /net/foo/backup/on-rfe - Restore from an explicit path 1223 ''' 1224 1225 if not os.getcwd().startswith(repo.root): 1226 raise util.Abort('restore is not safe to run with -R') 1227 1228 abort_if_dirty(wslist[repo]) 1229 1230 if opts['generation']: 1231 gen = int(opts['generation']) 1232 else: 1233 gen = None 1234 1235 if os.path.exists(backup): 1236 backup = os.path.abspath(backup) 1237 1238 wlock = repo.wlock() 1239 lock = repo.lock() 1240 1241 try: 1242 bk = CdmBackup(ui, wslist[repo], backup) 1243 bk.restore(gen) 1244 finally: 1245 lock.release() 1246 wlock.release() 1247 1248 1249 def cdm_webrev(ui, repo, **opts): 1250 '''generate web-based code review and optionally upload it 1251 1252 Generate a web-based code review using webrev(1) and optionally 1253 upload it. All known arguments are passed through to webrev(1). 1254 ''' 1255 1256 webrev_args = "" 1257 for key in opts.keys(): 1258 if opts[key]: 1259 if type(opts[key]) == type(True): 1260 webrev_args += '-' + key + ' ' 1261 else: 1262 webrev_args += '-' + key + ' ' + opts[key] + ' ' 1263 1264 retval = os.system('webrev ' + webrev_args) 1265 if retval != 0: 1266 return retval - 255 1267 1268 return 0 1269 1270 1271 def cdm_debugcdmal(ui, repo, *pats, **opts): 1272 '''dump the active list for the sake of debugging/testing''' 1273 1274 ui.write(wslist[repo].active(opts['parent']).as_text(pats)) 1275 1276 1277 def cdm_changed(ui, repo, *pats, **opts): 1278 '''mark a file as changed in the working copy 1279 1280 Maintain a list of files checked for modification in the working 1281 copy. If the list exists, most cadmium commands will only check 1282 the working copy for changes to those files, rather than checking 1283 the whole workspace (this does not apply to committed changes, 1284 which are always seen). 1285 1286 Since this list functions only as a hint as to where in the 1287 working copy to look for changes, entries that have not actually 1288 been modified (in the working copy, or in general) are not 1289 problematic. 1290 1291 1292 Note: If such a list exists, it must be kept up-to-date. 1293 1294 1295 Renamed files can be added with reference only to their new name: 1296 $ hg mv foo bar 1297 $ hg changed bar 1298 1299 Without arguments, 'hg changed' will list all files recorded as 1300 altered, such that, for instance: 1301 $ hg status $(hg changed) 1302 $ hg diff $(hg changed) 1303 Become useful (generally faster than their unadorned counterparts) 1304 1305 To create an initially empty list: 1306 $ hg changed -i 1307 Until files are added to the list it is equivalent to saying 1308 "Nothing has been changed" 1309 1310 Update the list based on the current active list: 1311 $ hg changed -u 1312 The old list is emptied, and replaced with paths from the 1313 current active list. 1314 1315 Remove the list entirely: 1316 $ hg changed -d 1317 ''' 1318 1319 def modded_files(repo, parent): 1320 out = wslist[repo].findoutgoing(wslist[repo].parent(parent)) 1321 outnodes = repo.changelog.nodesbetween(out)[0] 1322 1323 files = set() 1324 for n in outnodes: 1325 files.update(repo.changectx(n).files()) 1326 1327 files.update(wslist[repo].status().keys()) 1328 return files 1329 1330 # 1331 # specced_pats is convenient to treat as a boolean indicating 1332 # whether any file patterns or paths were specified. 1333 # 1334 specced_pats = pats or opts['include'] or opts['exclude'] 1335 if len(filter(None, [opts['delete'], opts['update'], opts['init'], 1336 specced_pats])) > 1: 1337 raise util.Abort("-d, -u, -i and patterns are mutually exclusive") 1338 1339 wl = WorkList(wslist[repo]) 1340 1341 if (not wl and specced_pats) or opts['init']: 1342 wl.delete() 1343 if yes_no(ui, "Create a list based on your changes thus far?", True): 1344 map(wl.add, modded_files(repo, opts.get('parent'))) 1345 1346 if opts['delete']: 1347 wl.delete() 1348 elif opts['update']: 1349 wl.delete() 1350 map(wl.add, modded_files(repo, opts.get('parent'))) 1351 wl.write() 1352 elif opts['init']: # Any possible old list was deleted above 1353 wl.write() 1354 elif specced_pats: 1355 sources = [] 1356 1357 match = wslist[repo].matcher(pats=pats, opts=opts) 1358 for abso in repo.walk(match): 1359 if abso in repo.dirstate: 1360 wl.add(abso) 1361 # 1362 # Store the source name of any copy. We use this so 1363 # both the add and delete of a rename can be entered 1364 # into the WorkList with only the destination name 1365 # explicitly being mentioned. 1366 # 1367 fctx = wslist[repo].workingctx().filectx(abso) 1368 rn = fctx.renamed() 1369 if rn: 1370 sources.append(rn[0]) 1371 else: 1372 ui.warn("%s is not version controlled -- skipping\n" % 1373 match.rel(abso)) 1374 1375 if sources: 1376 for fname, chng in wslist[repo].status(files=sources).iteritems(): 1377 if chng == 'removed': 1378 wl.add(fname) 1379 wl.write() 1380 else: 1381 for elt in sorted(wl.list()): 1382 ui.write("%s\n" % wslist[repo].filepath(elt)) 1383 1384 1385 cmdtable = { 1386 'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'), 1387 ('r', 'remain', None, 'do not change directory')], 1388 'hg apply [-p PARENT] [-r] command...'), 1389 '^backup|bu': (cdm_backup, [('t', 'if-newer', None, 1390 'only backup if workspace files are newer')], 1391 'hg backup [-t]'), 1392 'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')], 1393 'hg branchchk [-p PARENT]'), 1394 'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')], 1395 'hg bugs [-p PARENT]'), 1396 'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')], 1397 'hg cddlchk [-p PARENT]'), 1398 'changed': (cdm_changed, [('d', 'delete', None, 'delete the file list'), 1399 ('u', 'update', None, 'mark all changed files'), 1400 ('i', 'init', None, 'create an empty file list'), 1401 ('p', 'parent', '', 'parent workspace'), 1402 ('I', 'include', [], 1403 'include names matching the given patterns'), 1404 ('X', 'exclude', [], 1405 'exclude names matching the given patterns')], 1406 'hg changed -d\n' 1407 'hg changed -u\n' 1408 'hg changed -i\n' 1409 'hg changed [-I PATTERN...] [-X PATTERN...] [FILE...]'), 1410 'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'), 1411 ('N', 'nocheck', None, 1412 'do not compare comments with databases')], 1413 'hg comchk [-p PARENT]'), 1414 'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')], 1415 'hg comments [-p PARENT]'), 1416 'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')], 1417 'hg copyright [-p PARENT]'), 1418 'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')], 1419 'hg cstyle [-p PARENT]'), 1420 'debugcdmal': (cdm_debugcdmal, [('p', 'parent', '', 'parent workspace')], 1421 'hg debugcdmal [-p PARENT] [FILE...]'), 1422 'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'), 1423 ('r', 'remain', None, 'do not change directory')], 1424 'hg eval [-p PARENT] [-r] command...'), 1425 'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')], 1426 'hg hdrchk [-p PARENT]'), 1427 'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')], 1428 'hg jstyle [-p PARENT]'), 1429 'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')], 1430 'hg keywords [-p PARENT]'), 1431 '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'), 1432 ('a', 'added', None, 'show added files'), 1433 ('m', 'modified', None, 'show modified files'), 1434 ('r', 'removed', None, 'show removed files')], 1435 'hg list [-amrRu] [-p PARENT]'), 1436 'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')], 1437 'hg mapfilechk [-p PARENT]'), 1438 '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')], 1439 'hg nits [-p PARENT]'), 1440 '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'), 1441 ('N', 'nocheck', None, 'skip database checks')], 1442 'hg pbchk [-N] [-p PARENT]'), 1443 'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')], 1444 'hg permchk [-p PARENT]'), 1445 '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace'), 1446 ('a', 'text', None, 'treat all files as text'), 1447 ('g', 'git', None, 'use extended git diff format'), 1448 ('w', 'ignore-all-space', None, 1449 'ignore white space when comparing lines'), 1450 ('b', 'ignore-space-change', None, 1451 'ignore changes in the amount of white space'), 1452 ('B', 'ignore-blank-lines', None, 1453 'ignore changes whose lines are all blank'), 1454 ('U', 'unified', 3, 1455 'number of lines of context to show'), 1456 ('I', 'include', [], 1457 'include names matching the given patterns'), 1458 ('X', 'exclude', [], 1459 'exclude names matching the given patterns')], 1460 'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'), 1461 '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'), 1462 ('m', 'message', '', 1463 'use <text> as commit message'), 1464 ('l', 'logfile', '', 1465 'read commit message from file'), 1466 ('u', 'user', '', 1467 'record user as committer')], 1468 'hg recommit [-m TEXT] [-l FILE] [-u USER] [-p PARENT]'), 1469 'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')], 1470 'hg renamed [-p PARENT]'), 1471 'reparent': (cdm_reparent, [], 'hg reparent PARENT'), 1472 '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')], 1473 'hg restore [-g GENERATION] BACKUP'), 1474 'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')], 1475 'hg tagchk [-p PARENT]'), 1476 'webrev': (cdm_webrev, [('C', 'C', '', 'ITS priority file'), 1477 ('D', 'D', '', 'delete remote webrev'), 1478 ('I', 'I', '', 'ITS configuration file'), 1479 ('i', 'i', '', 'include file'), 1480 ('N', 'N', None, 'suppress comments'), 1481 ('n', 'n', None, 'do not generate webrev'), 1482 ('O', 'O', None, 'OpenSolaris mode'), 1483 ('o', 'o', '', 'output directory'), 1484 ('p', 'p', '', 'use specified parent'), 1485 ('t', 't', '', 'upload target'), 1486 ('U', 'U', None, 'upload the webrev'), 1487 ('w', 'w', '', 'use wx active file')], 1488 'hg webrev [WEBREV_OPTIONS]'), 1489 }