6805008 cdm.py needs updates to work with hg 1.1
[unleashed.git] / usr / src / tools / onbld / hgext / cdm.py
blobdce2333b464955137897c4d80885a0f0bf9a51c5
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.
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.
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.
17 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
18 # Use is subject to license terms.
21 '''workspace extensions for mercurial
23 This extension contains a number of commands to help you work within
24 the OpenSolaris consolidations.
26 Common uses:
28 Show diffs relative to parent workspace - pdiffs
29 Check source style rules - nits
30 Run pre-putback checks - pbchk
31 Collapse all your changes into a single changeset - recommit'''
35 # NB: This assumes the normal directory structure, with this
36 # extension 2 levels below .../lib/python.
38 # If you change that, change this
40 import sys, os, stat, termios, atexit
41 sys.path.insert(1, "%s/../../" % os.path.dirname(__file__))
43 from onbld.Scm import Version
44 from mercurial import util
46 try:
47 Version.check_version()
48 except Version.VersionMismatch, badversion:
49 raise util.Abort("Version Mismatch:\n %s\n" % badversion)
51 import ConfigParser
52 from mercurial import cmdutil, node, ignore
54 from onbld.Scm.WorkSpace import WorkSpace, ActiveEntry
55 from onbld.Scm.Backup import CdmBackup
56 from onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk
57 from onbld.Checks import JStyle, Keywords, Mapfile, Rti, onSWAN
60 def yes_no(ui, msg, default):
61 if default:
62 prompt = ' [Y/n]:'
63 defanswer = 'y'
64 else:
65 prompt = ' [y/N]:'
66 defanswer = 'n'
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
78 def _buildfilelist(repo, args):
79 '''build a list of files in which we're interested
81 If no files are specified, then we'll default to using
82 the entire active list.
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.'''
89 fdict = {}
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.
98 if args:
99 for f in args:
100 fdict[f] = None
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.
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.
113 else:
114 active = wslist[repo].active()
115 for e in sorted(active):
116 fdict[wslist[repo].filepath(e.name)] = e
118 return fdict
121 def not_check(repo, cmd):
122 '''return a function which returns boolean indicating whether a file
123 should be skipped for CMD.'''
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.
129 # Wrap our argument such that the path is canonified before it is checked.
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
137 ignorefiles = []
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)
144 if ignorefiles:
145 ign = ignore.ignore(repo.root, ignorefiles, repo.ui.warn)
146 return canonified_check(ign)
147 else:
148 return util.never
152 # Adding a reference to WorkSpace from a repo causes a circular reference
153 # repo <-> WorkSpace.
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.
160 # Instead, we hold the repo->WorkSpace association in a dictionary, breaking
161 # that dependence.
163 wslist = {}
166 def reposetup(ui, repo):
167 if repo.local() and repo not in wslist:
168 wslist[repo] = WorkSpace(repo)
170 if ui.interactive and sys.stdin.isatty():
171 ui.setconfig('hooks', 'preoutgoing.cdm_pbconfirm',
172 'python:hgext_cdm.pbconfirm')
175 def pbconfirm(ui, repo, hooktype, source):
176 def wrapper(settings=None):
177 termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings)
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)
190 def cdm_pdiffs(ui, repo, *pats, **opts):
191 '''list workspace diffs relative to parent workspace
193 The parent tip is taken to be the latest revision shared between
194 us and the parent workspace.'''
196 parent = opts['parent']
198 diffs = wslist[repo].pdiff(pats, opts, parent=parent)
199 if diffs:
200 ui.write(diffs)
203 def cdm_list(ui, repo, **opts):
204 '''list files changed relative to parent workspace
206 The parent tip is taken to be the latest revision shared between
207 us and the parent workspace.'''
209 wanted = []
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)
218 act = wslist[repo].active(opts['parent'])
219 chngmap = {ActiveEntry.MODIFIED: 'modified',
220 ActiveEntry.ADDED: 'added',
221 ActiveEntry.REMOVED: 'removed'}
223 lst = {}
224 for entry in act:
225 if wanted and (entry.change not in wanted):
226 continue
228 chngstr = chngmap[entry.change]
229 if chngstr not in lst:
230 lst[chngstr] = []
231 lst[chngstr].append(entry)
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)
246 def cdm_arcs(ui, repo, parent=None):
247 'show all ARC cases in checkin comments'
248 act = wslist[repo].active(parent)
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')
255 def cdm_bugs(ui, repo, parent=None):
256 'show all bug IDs in checkin comments'
257 act = wslist[repo].active(parent)
259 for elt in set(filter(Comments.isBug, act.comments())):
260 ui.write(elt + '\n')
263 def cdm_comments(ui, repo, parent=None):
264 'show checkin comments for active files'
265 act = wslist[repo].active(parent)
267 for elt in act.comments():
268 ui.write(elt + '\n')
271 def cdm_renamed(ui, repo, parent=None):
272 '''show renamed active files
274 Renamed files are shown in the format
276 newname oldname
278 One pair per-line.'''
280 act = wslist[repo].active(parent)
282 for entry in sorted(filter(lambda x: x.is_renamed(), act)):
283 ui.write('%s %s\n' % (entry.name, entry.parentname))
286 def cdm_comchk(ui, repo, **opts):
287 '''check checkin comments for active files
289 Check that checkin comments conform to O/N rules.'''
291 active = wslist[repo].active(opts.get('parent'))
293 ui.write('Comments check:\n')
295 check_db = not opts.get('nocheck')
296 return Comments.comchk(active.comments(), check_db=check_db, output=ui)
299 def cdm_cddlchk(ui, repo, *args, **opts):
300 '''check for a valid CDDL block in active files
302 See http://www.opensolaris.org/os/community/on/devref_toc/devref_7/#7_2_3_nonformatting_considerations
303 for more info.'''
305 filelist = opts.get('filelist') or _buildfilelist(repo, args)
307 ui.write('CDDL block check:\n')
309 lenient = True
310 ret = 0
312 exclude = not_check(repo, 'cddlchk')
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
325 fh = open(f, 'r')
326 ret |= Cddl.cddlchk(fh, lenient=lenient, output=ui)
327 fh.close()
328 return ret
331 def cdm_mapfilechk(ui, repo, *args, **opts):
332 '''check for a valid MAPFILE header block in active files
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).'''
338 filelist = opts.get('filelist') or _buildfilelist(repo, args)
340 ui.write('Mapfile comment check:\n')
342 ret = 0
343 exclude = not_check(repo, 'mapfilechk')
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
354 fh = open(f, 'r')
355 ret |= Mapfile.mapfilechk(fh, output=ui)
356 fh.close()
357 return ret
360 def cdm_copyright(ui, repo, *args, **opts):
361 '''check active files for valid copyrights
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.'''
368 filelist = opts.get('filelist') or _buildfilelist(repo, args)
370 ui.write('Copyright check:\n')
372 ret = 0
373 exclude = not_check(repo, 'copyright')
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
382 fh = open(f, 'r')
383 ret |= Copyright.copyright(fh, output=ui)
384 fh.close()
385 return ret
388 def cdm_hdrchk(ui, repo, *args, **opts):
389 '''check active header files conform to O/N rules'''
391 filelist = opts.get('filelist') or _buildfilelist(repo, args)
393 ui.write('Header format check:\n')
395 ret = 0
396 exclude = not_check(repo, 'hdrchk')
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
407 fh = open(f, 'r')
408 ret |= HdrChk.hdrchk(fh, lenient=True, output=ui)
409 fh.close()
410 return ret
413 def cdm_cstyle(ui, repo, *args, **opts):
414 '''check active C source files conform to the C Style Guide
416 See http://opensolaris.org/os/community/documentation/getting_started_docs/cstyle.ms.pdf'''
418 filelist = opts.get('filelist') or _buildfilelist(repo, args)
420 ui.write('C style check:\n')
422 ret = 0
423 exclude = not_check(repo, 'cstyle')
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
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
442 def cdm_jstyle(ui, repo, *args, **opts):
443 'check active Java source files for common stylistic errors'
445 filelist = opts.get('filelist') or _buildfilelist(repo, args)
447 ui.write('Java style check:\n')
449 ret = 0
450 exclude = not_check(repo, 'jstyle')
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
461 fh = open(f, 'r')
462 ret |= JStyle.jstyle(fh, output=ui, picky=True)
463 fh.close()
464 return ret
467 def cdm_permchk(ui, repo, *args, **opts):
468 '''check active files permission - warn +x (execute) mode'''
470 filelist = opts.get('filelist') or _buildfilelist(repo, args)
472 ui.write('File permission check:\n')
474 exeFiles = []
475 exclude = not_check(repo, 'permchk')
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
484 mode = stat.S_IMODE(os.stat(f)[stat.ST_MODE])
485 if mode & stat.S_IEXEC:
486 exeFiles.append(f)
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)
494 return len(exeFiles) > 0
497 def cdm_tagchk(ui, repo, **opts):
498 '''check if .hgtags is active and issue warning
500 Tag sharing among repositories is restricted to gatekeepers'''
502 active = wslist[repo].active(opts.get('parent'))
504 ui.write('Checking for new tags:\n')
506 if ".hgtags" in active:
507 tfile = wslist[repo].filepath('.hgtags')
508 ptip = active.parenttip.rev()
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))
518 return 1
520 return 0
523 def cdm_branchchk(ui, repo, **opts):
524 '''check if multiple heads (or branches) are present, or if
525 branch changes are made'''
527 ui.write('Checking for multiple heads (or branches):\n')
529 heads = set(repo.heads())
530 parents = set([x.node() for x in wslist[repo].workingctx().parents()])
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).
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
546 ui.write('\nChecking for branch changes:\n')
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
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)
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
573 return 0
576 def cdm_rtichk(ui, repo, **opts):
577 '''check active bug/RFEs for approved RTIs
579 Only works on SWAN.'''
581 if opts.get('nocheck') or os.path.exists(repo.join('cdm/rtichk.NOT')):
582 ui.status('Skipping RTI checks...\n')
583 return 0
585 if not onSWAN():
586 ui.write('RTI checks only work on SWAN, skipping...\n')
587 return 0
589 parent = wslist[repo].parent(opts.get('parent'))
590 active = wslist[repo].active(parent)
592 ui.write('RTI check:\n')
594 bugs = []
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))
601 # RTI normalizes the gate path for us
602 return int(not Rti.rti(bugs, gatePath=parent, output=ui))
605 def cdm_keywords(ui, repo, *args, **opts):
606 '''check source files do not contain SCCS keywords'''
608 filelist = opts.get('filelist') or _buildfilelist(repo, args)
610 ui.write('Keywords check:\n')
612 ret = 0
613 exclude = not_check(repo, 'keywords')
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
622 fh = open(f, 'r')
623 ret |= Keywords.keywords(fh, output=ui)
624 fh.close()
625 return ret
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.
633 def cdm_outchk(ui, repo, **opts):
634 '''Warn the user if they have uncommitted changes'''
636 ui.write('Checking for uncommitted changes:\n')
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
647 def cdm_mergechk(ui, repo, **opts):
648 '''Warn the user if their workspace contains merges'''
650 active = wslist[repo].active(opts.get('parent'))
652 ui.write('Checking for merges:\n')
654 merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1],
655 active.revs)
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
668 def run_checks(ws, cmds, *args, **opts):
669 '''Run CMDS (with OPTS) over active files in WS'''
671 ret = 0
673 flist = _buildfilelist(ws.repo, args)
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()
682 result = cmd(ws.ui, ws.repo, filelist=flist,
683 honour_nots=True, *args, **opts)
684 ret |= result
686 output = ws.ui.popbuffer()
687 if not ws.ui.quiet or result != 0:
688 ws.ui.write(output, '\n')
689 return ret
692 def cdm_nits(ui, repo, *args, **opts):
693 '''check for stylistic nits in active files
695 Run cddlchk, copyright, cstyle, hdrchk, jstyle, mapfilechk,
696 permchk, and keywords checks.'''
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]
707 return run_checks(wslist[repo], cmds, *args, **opts)
710 def cdm_pbchk(ui, repo, *args, **opts):
711 '''pre-putback check all active files
713 Run cddlchk, comchk, copyright, cstyle, hdrchk, jstyle, mapfilechk,
714 permchk, tagchk, branchchk, keywords and rtichk checks. Additionally,
715 warn about uncommitted changes.'''
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
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]
737 return run_checks(wslist[repo], cmds, *args, **opts)
740 def cdm_recommit(ui, repo, **opts):
741 '''compact outgoing deltas into a single, conglomerate delta'''
743 if not os.getcwd().startswith(repo.root):
744 raise util.Abort('recommit is not safe to run with -R')
746 if wslist[repo].modified():
747 raise util.Abort('workspace has uncommitted changes')
749 if wslist[repo].merged():
750 raise util.Abort('workspace contains uncommitted merge')
752 if wslist[repo].branched():
753 raise util.Abort('workspace contains uncommitted branch')
755 if wslist[repo].mq_applied():
756 raise util.Abort("workspace has Mq patches applied")
758 wlock = repo.wlock()
759 lock = repo.lock()
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')
768 active = wslist[repo].active(opts['parent'])
770 if len(active.revs) <= 0:
771 raise util.Abort("no changes to recommit")
773 if len(active.files()) <= 0:
774 ui.warn("Recommitting %d active changesets, but no active files\n" %
775 len(active.revs))
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.
781 # As such, we ask the user before proceeding.
783 bogosity = [f.parentname for f in active if f.is_renamed() and
784 os.path.exists(repo.wjoin(f.parentname))]
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")
796 user = opts['user'] or ui.username()
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')
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()
809 oldtags = repo.tags()
810 clearedtags = [(name, nd, repo.changelog.rev(nd), local)
811 for name, nd, local in active.tags()]
813 wslist[repo].squishdeltas(active, message, user=user)
815 if clearedtags:
816 ui.write("Removed tags:\n")
817 for name, nd, rev, local in clearedtags:
818 ui.write(" %s %s:%s%s\n" % (name, rev, node.short(nd),
819 (local and ' (local)') or ''))
821 for ntag, nnode in repo.tags().items():
822 if ntag in oldtags and ntag != "tip":
823 if oldtags[ntag] != nnode:
824 ui.write("tag %s now refers to revision %d:%s\n" %
825 (ntag, repo.changelog.rev(nnode), node.short(nnode)))
828 def do_eval(cmd, files, root, changedir=True):
829 if not changedir:
830 os.chdir(root)
832 for path in sorted(files):
833 dirn, base = os.path.split(path)
835 if changedir:
836 os.chdir(os.path.join(root, dirn))
838 os.putenv('workspace', root)
839 os.putenv('filepath', path)
840 os.putenv('dir', dirn)
841 os.putenv('file', base)
842 os.system(cmd)
845 def cdm_eval(ui, repo, *command, **opts):
846 '''run cmd for each active file
848 cmd can refer to:
849 $file - active file basename.
850 $dir - active file dirname.
851 $filepath - path from workspace root to active file.
852 $workspace - full path to workspace root.
854 For example "hg eval 'echo $dir; hg log -l3 $file'" will show the last
855 the 3 log entries for each active file, preceded by its directory.'''
857 act = wslist[repo].active(opts['parent'])
858 cmd = ' '.join(command)
859 files = [x.name for x in act if not x.is_removed()]
861 do_eval(cmd, files, repo.root, not opts['remain'])
864 def cdm_apply(ui, repo, *command, **opts):
865 '''apply cmd to all active files
867 For example 'hg apply wc -l' outputs a line count of active files.'''
869 act = wslist[repo].active(opts['parent'])
871 if opts['remain']:
872 appnd = ' $filepath'
873 else:
874 appnd = ' $file'
876 cmd = ' '.join(command) + appnd
877 files = [x.name for x in act if not x.is_removed()]
879 do_eval(cmd, files, repo.root, not opts['remain'])
882 def cdm_reparent(ui, repo, parent):
883 '''reparent your workspace
885 Updates the 'default' path.'''
887 filename = repo.join('hgrc')
889 p = ui.expandpath(parent)
890 if not p:
891 raise util.Abort("could not find parent: %s" % parent)
893 cp = util.configparser()
894 try:
895 cp.read(filename)
896 except ConfigParser.ParsingError, inst:
897 raise util.Abort('failed to parse %s\n%s' % (filename, inst))
899 try:
900 fh = open(filename, 'w')
901 except IOError, e:
902 raise util.Abort('Failed to open workspace configuration: %s' % e)
904 if not cp.has_section('paths'):
905 cp.add_section('paths')
906 cp.set('paths', 'default', p)
907 cp.write(fh)
908 fh.close()
911 def backup_name(fullpath):
912 '''Create a backup directory name based on the specified path.
914 In most cases this is the basename of the path specified, but
915 certain cases are handled specially to create meaningful names'''
917 special = ['usr/closed']
919 fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep)
922 # If a path is 'special', we append the basename of the path to
923 # the path element preceding the constant, special, part.
925 # Such that for instance:
926 # /foo/bar/onnv-fixes/usr/closed
927 # has a backup name of:
928 # onnv-fixes-closed
930 for elt in special:
931 elt = elt.split(os.path.sep)
932 pathpos = len(elt)
934 if fullpath[-pathpos:] == elt:
935 return "%s-%s" % (fullpath[-pathpos - 1], elt[-1])
936 else:
937 return fullpath[-1]
940 def cdm_backup(ui, repo, if_newer=False):
941 '''make backup copies of all workspace changes
943 Backups will be stored in ~/cdm.backup/<basename of workspace>.'''
945 name = backup_name(repo.root)
946 bk = CdmBackup(ui, wslist[repo], name)
948 if if_newer and not bk.need_backup():
949 ui.status('backup is up-to-date\n')
950 else:
951 bk.backup()
954 def cdm_restore(ui, repo, backup, **opts):
955 '''restore workspace from backup
957 Restores a workspace from the specified backup directory and generation
958 (which defaults to the latest).'''
960 if not os.getcwd().startswith(repo.root):
961 raise util.Abort('restore is not safe to run with -R')
962 if wslist[repo].modified():
963 raise util.Abort('Workspace has uncommitted changes')
964 if wslist[repo].merged():
965 raise util.Abort('Workspace has an uncommitted merge')
966 if wslist[repo].branched():
967 raise util.Abort('Workspace has an uncommitted branch')
969 if opts['generation']:
970 gen = int(opts['generation'])
971 else:
972 gen = None
974 if os.path.exists(backup):
975 backup = os.path.abspath(backup)
977 bk = CdmBackup(ui, wslist[repo], backup)
978 bk.restore(gen)
981 def cdm_webrev(ui, repo, **opts):
982 '''generate webrev and optionally upload it
984 This command passes all arguments to webrev script'''
986 webrev_args = ""
987 for key in opts.keys():
988 if opts[key]:
989 if type(opts[key]) == type(True):
990 webrev_args += '-' + key + ' '
991 else:
992 webrev_args += '-' + key + ' ' + opts[key] + ' '
994 retval = os.system('webrev ' + webrev_args)
995 if retval != 0:
996 return retval - 255
998 return 0
1001 cmdtable = {
1002 'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'),
1003 ('r', 'remain', None, 'do not change directories')],
1004 'hg apply [-p PARENT] [-r] command...'),
1005 'arcs': (cdm_arcs, [('p', 'parent', '', 'parent workspace')],
1006 'hg arcs [-p PARENT]'),
1007 '^backup|bu': (cdm_backup, [('t', 'if-newer', None,
1008 'only backup if workspace files are newer')],
1009 'hg backup [-t]'),
1010 'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')],
1011 'hg branchchk [-p PARENT]'),
1012 'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')],
1013 'hg bugs [-p PARENT]'),
1014 'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')],
1015 'hg cddlchk [-p PARENT]'),
1016 'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'),
1017 ('N', 'nocheck', None,
1018 'do not compare comments with databases')],
1019 'hg comchk [-p PARENT]'),
1020 'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')],
1021 'hg comments [-p PARENT]'),
1022 'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')],
1023 'hg copyright [-p PARENT]'),
1024 'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')],
1025 'hg cstyle [-p PARENT]'),
1026 'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'),
1027 ('r', 'remain', None, 'do not change directories')],
1028 'hg eval [-p PARENT] [-r] command...'),
1029 'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')],
1030 'hg hdrchk [-p PARENT]'),
1031 'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')],
1032 'hg jstyle [-p PARENT]'),
1033 'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')],
1034 'hg keywords [-p PARENT]'),
1035 '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'),
1036 ('r', 'removed', None, 'show removed files'),
1037 ('a', 'added', None, 'show added files'),
1038 ('m', 'modified', None, 'show modified files')],
1039 'hg list [-amrRu] [-p PARENT]'),
1040 'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')],
1041 'hg mapfilechk [-p PARENT]'),
1042 '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')],
1043 'hg nits [-p PARENT]'),
1044 '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'),
1045 ('N', 'nocheck', None, 'skip RTI check')],
1046 'hg pbchk [-N] [-p PARENT]'),
1047 'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')],
1048 'hg permchk [-p PARENT]'),
1049 '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace'),
1050 ('a', 'text', None, 'treat all files as text'),
1051 ('g', 'git', None, 'use extended git diff format'),
1052 ('w', 'ignore-all-space', None,
1053 'ignore white space when comparing lines'),
1054 ('b', 'ignore-space-change', None,
1055 'ignore changes in the amount of white space'),
1056 ('B', 'ignore-blank-lines', None,
1057 'ignore changes whos lines are all blank'),
1058 ('U', 'unified', 3,
1059 'number of lines of context to show'),
1060 ('I', 'include', [],
1061 'include names matching the given patterns'),
1062 ('X', 'exclude', [],
1063 'exclude names matching the given patterns')],
1064 'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'),
1065 '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'),
1066 ('f', 'force', None, 'force operation'),
1067 ('m', 'message', '',
1068 'use <text> as commit message'),
1069 ('l', 'logfile', '',
1070 'read commit message from file'),
1071 ('u', 'user', '',
1072 'record user as committer')],
1073 'hg recommit [-f] [-p PARENT]'),
1074 'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')],
1075 'hg renamed [-p PARENT]'),
1076 'reparent': (cdm_reparent, [], 'hg reparent PARENT'),
1077 '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')],
1078 'hg restore [-g GENERATION] BACKUP'),
1079 'rtichk': (cdm_rtichk, [('p', 'parent', '', 'parent workspace'),
1080 ('N', 'nocheck', None, 'skip RTI check')],
1081 'hg rtichk [-N] [-p PARENT]'),
1082 'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')],
1083 'hg tagchk [-p PARENT]'),
1084 'webrev': (cdm_webrev, [('D', 'D', '', 'delete remote webrev'),
1085 ('i', 'i', '', 'include file'),
1086 ('l', 'l', '', 'extract file list from putback -n'),
1087 ('N', 'N', None, 'supress comments'),
1088 ('n', 'n', None, 'do not generate webrev'),
1089 ('O', 'O', None, 'OpenSolaris mode'),
1090 ('o', 'o', '', 'output directory'),
1091 ('p', 'p', '', 'use specified parent'),
1092 ('t', 't', '', 'upload target'),
1093 ('U', 'U', None, 'upload the webrev'),
1094 ('w', 'w', '', 'use wx active file')],
1095 'hg webrev [WEBREV_OPTIONS]'),