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