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