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