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