| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License version 2 |
| # as published by the Free Software Foundation. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| # |
| |
| # |
| # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. |
| # Copyright 2008, 2011 Richard Lowe |
| # Copyright 2014 Garrett D'Amore <garrett@damore.org> |
| # |
| |
| '''OpenSolaris extensions to Mercurial |
| |
| This extension contains a number of commands to help you work with |
| the OpenSolaris consolidations. It provides commands to check your |
| changes against the various style rules used for OpenSolaris, to |
| backup and restore your changes, to generate code reviews, and to |
| prepare your changes for integration. |
| |
| |
| The Parent |
| |
| To provide a uniform notion of parent workspace regardless of |
| filesystem-based access, Cadmium uses the highest numbered changeset |
| on the current branch that is also in the parent workspace to |
| represent the parent workspace. |
| |
| |
| The Active List |
| |
| Many Cadmium commands operate on the active list, the set of |
| files ('active files') you have changed in this workspace in relation |
| to its parent workspace, and the metadata (commentary, primarily) |
| associated with those changes. |
| |
| |
| NOT Files |
| |
| Many of Cadmium's commands to check that your work obeys the |
| various stylistic rules of the OpenSolaris consolidations (such as |
| those run by 'hg nits') allow files to be excluded from this checking |
| by means of NOT files kept in the .hg/cdm/ directory of the Mercurial |
| repository for one-time exceptions, and in the exception_lists |
| directory at the repository root for permanent exceptions. (For ON, |
| these would mean one in $CODEMGR_WS and one in |
| $CODEMGR_WS/usr/closed). |
| |
| These files are in the same format as the Mercurial hgignore |
| file, a description of which is available in the hgignore(5) manual |
| page. |
| |
| |
| Common Tasks |
| |
| - Show diffs relative to parent workspace - pdiffs |
| - Check source style rules - nits |
| - Run pre-integration checks - pbchk |
| - Collapse all your changes into a single changeset - recommit |
| ''' |
| |
| import atexit, os, re, sys, stat, termios |
| |
| |
| # |
| # Adjust the load path based on the location of cdm.py and the version |
| # of python into which it is being loaded. This assumes the normal |
| # onbld directory structure, where cdm.py is in |
| # lib/python(version)?/onbld/hgext/. If that changes so too must |
| # this. |
| # |
| # This and the case below are not equivalent. In this case we may be |
| # loading a cdm.py in python2.X/ via the lib/python/ symlink but need |
| # python2.Y in sys.path. |
| # |
| sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "..", "..", |
| "python%d.%d" % sys.version_info[:2])) |
| |
| # |
| # Add the relative path from cdm.py to usr/src/tools to the load path, |
| # such that a cdm.py loaded from the source tree uses the modules also |
| # within the source tree. |
| # |
| sys.path.insert(2, os.path.join(os.path.dirname(__file__), "..", "..")) |
| |
| from onbld.Scm import Version |
| from mercurial import util |
| |
| try: |
| Version.check_version() |
| except Version.VersionMismatch, badversion: |
| raise util.Abort("Version Mismatch:\n %s\n" % badversion) |
| |
| from mercurial import cmdutil, ignore, node, patch |
| |
| from onbld.Scm.WorkSpace import WorkSpace, WorkList |
| from onbld.Scm.Backup import CdmBackup |
| from onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk |
| from onbld.Checks import JStyle, Keywords, ManLint, Mapfile |
| |
| |
| def yes_no(ui, msg, default): |
| if default: |
| prompt = ' [Y/n]:' |
| defanswer = 'y' |
| else: |
| prompt = ' [y/N]:' |
| defanswer = 'n' |
| |
| if Version.at_least("1.4"): |
| index = ui.promptchoice(msg + prompt, ['&yes', '&no'], |
| default=['y', 'n'].index(defanswer)) |
| resp = ('y', 'n')[index] |
| else: |
| resp = ui.prompt(msg + prompt, ['&yes', '&no'], default=defanswer) |
| |
| return resp[0] in ('Y', 'y') |
| |
| |
| def buildfilelist(ws, parent, files): |
| '''Build a list of files in which we're interested. |
| |
| If no files are specified take files from the active list relative |
| to 'parent'. |
| |
| Return a list of 2-tuples the first element being a path relative |
| to the current directory and the second an entry from the active |
| list, or None if an explicit file list was given.''' |
| |
| if files: |
| return [(path, None) for path in sorted(files)] |
| else: |
| active = ws.active(parent=parent) |
| return [(ws.filepath(e.name), e) for e in sorted(active)] |
| buildfilelist = util.cachefunc(buildfilelist) |
| |
| |
| def not_check(repo, cmd): |
| '''return a function which returns boolean indicating whether a file |
| should be skipped for CMD.''' |
| |
| # |
| # The ignore routines need a canonical path to the file (relative to the |
| # repo root), whereas the check commands get paths relative to the cwd. |
| # |
| # Wrap our argument such that the path is canonified before it is checked. |
| # |
| def canonified_check(ignfunc): |
| def f(path): |
| cpath = util.canonpath(repo.root, repo.getcwd(), path) |
| return ignfunc(cpath) |
| return f |
| |
| ignorefiles = [] |
| |
| for f in [repo.join('cdm/%s.NOT' % cmd), |
| repo.wjoin('exception_lists/%s' % cmd)]: |
| if os.path.exists(f): |
| ignorefiles.append(f) |
| |
| if ignorefiles: |
| ign = ignore.ignore(repo.root, ignorefiles, repo.ui.warn) |
| return canonified_check(ign) |
| else: |
| return util.never |
| |
| |
| def abort_if_dirty(ws): |
| '''Abort if the workspace has uncommitted changes, merges, |
| branches, or has Mq patches applied''' |
| |
| if ws.modified(): |
| raise util.Abort('workspace has uncommitted changes') |
| if ws.merged(): |
| raise util.Abort('workspace contains uncommitted merge') |
| if ws.branched(): |
| raise util.Abort('workspace contains uncommitted branch') |
| if ws.mq_applied(): |
| raise util.Abort('workspace has Mq patches applied') |
| |
| |
| # |
| # Adding a reference to WorkSpace from a repo causes a circular reference |
| # repo <-> WorkSpace. |
| # |
| # This prevents repo, WorkSpace and members thereof from being garbage |
| # collected. Since transactions are aborted when the transaction object |
| # is collected, and localrepo holds a reference to the most recently created |
| # transaction, this prevents transactions from cleanly aborting. |
| # |
| # Instead, we hold the repo->WorkSpace association in a dictionary, breaking |
| # that dependence. |
| # |
| wslist = {} |
| |
| |
| def reposetup(ui, repo): |
| if repo.local() and repo not in wslist: |
| wslist[repo] = WorkSpace(repo) |
| |
| if ui.interactive() and sys.stdin.isatty(): |
| ui.setconfig('hooks', 'preoutgoing.cdm_pbconfirm', |
| 'python:hgext_cdm.pbconfirm') |
| |
| |
| def pbconfirm(ui, repo, hooktype, source): |
| def wrapper(settings=None): |
| termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings) |
| |
| if source == 'push': |
| if not yes_no(ui, "Are you sure you wish to push?", False): |
| return 1 |
| else: |
| settings = termios.tcgetattr(sys.stdin.fileno()) |
| orig = list(settings) |
| atexit.register(wrapper, orig) |
| settings[3] = settings[3] & (~termios.ISIG) # c_lflag |
| termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings) |
| |
| |
| def cdm_pdiffs(ui, repo, *pats, **opts): |
| '''diff workspace against its parent |
| |
| Show differences between this workspace and its parent workspace |
| in the same manner as 'hg diff'. |
| |
| For a description of the changeset used to represent the parent |
| workspace, see The Parent in the extension documentation ('hg help |
| cdm'). |
| ''' |
| |
| act = wslist[repo].active(opts.get('parent')) |
| if not act.revs: |
| return |
| |
| # |
| # If no patterns were specified, either explicitly or via -I or -X |
| # use the active list files to avoid a workspace walk. |
| # |
| if pats or opts.get('include') or opts.get('exclude'): |
| matchfunc = wslist[repo].matcher(pats=pats, opts=opts) |
| else: |
| matchfunc = wslist[repo].matcher(files=act.files()) |
| |
| opts = patch.diffopts(ui, opts) |
| diffs = wslist[repo].diff(act.parenttip.node(), act.localtip.node(), |
| match=matchfunc, opts=opts) |
| if diffs: |
| ui.write(diffs) |
| |
| |
| def cdm_list(ui, repo, **opts): |
| '''list active files (those changed in this workspace) |
| |
| Display a list of files changed in this workspace as compared to |
| its parent workspace. |
| |
| File names are displayed one-per line, grouped by manner in which |
| they changed (added, modified, removed). Information about |
| renames or copies is output in parentheses following the file |
| name. |
| |
| For a description of the changeset used to represent the parent |
| workspace, see The Parent in the extension documentation ('hg help |
| cdm'). |
| |
| Output can be filtered by change type with --added, --modified, |
| and --removed. By default, all files are shown. |
| ''' |
| |
| act = wslist[repo].active(opts['parent']) |
| wanted = set(x for x in ('added', 'modified', 'removed') if opts[x]) |
| changes = {} |
| |
| for entry in act: |
| if wanted and (entry.change not in wanted): |
| continue |
| |
| if entry.change not in changes: |
| changes[entry.change] = [] |
| changes[entry.change].append(entry) |
| |
| for change in sorted(changes.keys()): |
| ui.write(change + ':\n') |
| |
| for entry in sorted(changes[change]): |
| if entry.is_renamed(): |
| ui.write('\t%s (renamed from %s)\n' % (entry.name, |
| entry.parentname)) |
| elif entry.is_copied(): |
| ui.write('\t%s (copied from %s)\n' % (entry.name, |
| entry.parentname)) |
| else: |
| ui.write('\t%s\n' % entry.name) |
| |
| |
| def cdm_bugs(ui, repo, parent=None): |
| '''show all bug IDs referenced in changeset comments''' |
| |
| act = wslist[repo].active(parent) |
| |
| for elt in set(filter(Comments.isBug, act.comments())): |
| ui.write(elt + '\n') |
| |
| |
| def cdm_comments(ui, repo, parent=None): |
| '''show changeset commentary for all active changesets''' |
| act = wslist[repo].active(parent) |
| |
| for elt in act.comments(): |
| ui.write(elt + '\n') |
| |
| |
| def cdm_renamed(ui, repo, parent=None): |
| '''show renamed active files |
| |
| Renamed files are shown in the format:: |
| |
| new-name old-name |
| |
| One pair per-line. |
| ''' |
| |
| act = wslist[repo].active(parent) |
| |
| for entry in sorted(filter(lambda x: x.is_renamed(), act)): |
| ui.write('%s %s\n' % (entry.name, entry.parentname)) |
| |
| |
| def cdm_comchk(ui, repo, **opts): |
| '''check active changeset comment formatting |
| |
| Check that active changeset comments conform to O/N rules. |
| |
| Each comment line must contain either one bug or ARC case ID |
| followed by its synopsis, or credit an external contributor. |
| ''' |
| |
| active = wslist[repo].active(opts.get('parent')) |
| |
| ui.write('Comments check:\n') |
| |
| check_db = not opts.get('nocheck') |
| return Comments.comchk(active.comments(), check_db=check_db, output=ui) |
| |
| |
| def cdm_cddlchk(ui, repo, *args, **opts): |
| '''check for a valid CDDL header comment in all active files. |
| |
| Check active files for a valid Common Development and Distribution |
| License (CDDL) block comment. |
| |
| Newly added files are checked for a copy of the CDDL header |
| comment. Modified files are only checked if they contain what |
| appears to be an existing CDDL header comment. |
| |
| Files can be excluded from this check using the cddlchk.NOT file. |
| See NOT Files in the extension documentation ('hg help cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'cddlchk') |
| lenient = True |
| ret = 0 |
| |
| ui.write('CDDL block check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| elif e and e.is_added(): |
| lenient = False |
| else: |
| lenient = True |
| |
| fh = open(f, 'r') |
| ret |= Cddl.cddlchk(fh, lenient=lenient, output=ui) |
| fh.close() |
| return ret |
| |
| |
| def cdm_manlintchk(ui, repo, *args, **opts): |
| '''check for mandoc lint |
| |
| Check for man page formatting errors. |
| |
| Files can be excluded from this check using the manlint.NOT |
| file. See NOT Files in the extension documentation ('hg help |
| cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'manlint') |
| ret = 0 |
| |
| # Man pages are identified as having a suffix starting with a digit. |
| ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE) |
| |
| ui.write('Man format check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif (not ManfileRE.match(f)): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| |
| fh = open(f, 'r') |
| ret |= ManLint.manlint(fh, output=ui, picky=True) |
| fh.close() |
| return ret |
| |
| |
| def cdm_mapfilechk(ui, repo, *args, **opts): |
| '''check for a valid mapfile header block in active files |
| |
| Check that all link-editor mapfiles contain the standard mapfile |
| header comment directing the reader to the document containing |
| Solaris object versioning rules (README.mapfile). |
| |
| Files can be excluded from this check using the mapfilechk.NOT |
| file. See NOT Files in the extension documentation ('hg help |
| cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'mapfilechk') |
| ret = 0 |
| |
| # We are interested in examining any file that has the following |
| # in its final path segment: |
| # - Contains the word 'mapfile' |
| # - Begins with 'map.' |
| # - Ends with '.map' |
| # We don't want to match unless these things occur in final path segment |
| # because directory names with these strings don't indicate a mapfile. |
| # We also ignore files with suffixes that tell us that the files |
| # are not mapfiles. |
| MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$', |
| re.IGNORECASE) |
| NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE) |
| |
| ui.write('Mapfile comment check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif (not MapfileRE.match(f)) or NotMapSuffixRE.match(f): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| |
| fh = open(f, 'r') |
| ret |= Mapfile.mapfilechk(fh, output=ui) |
| fh.close() |
| return ret |
| |
| |
| def cdm_copyright(ui, repo, *args, **opts): |
| '''check each active file for a current and correct copyright notice |
| |
| Check that all active files have a correctly formed copyright |
| notice containing the current year. |
| |
| See the Non-Formatting Considerations section of the OpenSolaris |
| Developer's Reference for more info on the correct form of |
| copyright notice. |
| (http://hub.opensolaris.org/bin/view/Community+Group+on/devref_7#H723NonFormattingConsiderations) |
| |
| Files can be excluded from this check using the copyright.NOT file. |
| See NOT Files in the extension documentation ('hg help cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'copyright') |
| ret = 0 |
| |
| ui.write('Copyright check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| |
| fh = open(f, 'r') |
| ret |= Copyright.copyright(fh, output=ui) |
| fh.close() |
| return ret |
| |
| |
| def cdm_hdrchk(ui, repo, *args, **opts): |
| '''check active C header files conform to the O/N header rules |
| |
| Check that any added or modified C header files conform to the O/N |
| header rules. |
| |
| See the section 'HEADER STANDARDS' in the hdrchk(1) manual page |
| for more information on the rules for O/N header file formatting. |
| |
| Files can be excluded from this check using the hdrchk.NOT file. |
| See NOT Files in the extension documentation ('hg help cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'hdrchk') |
| ret = 0 |
| |
| ui.write('Header format check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif not f.endswith('.h'): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| |
| fh = open(f, 'r') |
| ret |= HdrChk.hdrchk(fh, lenient=True, output=ui) |
| fh.close() |
| return ret |
| |
| |
| def cdm_cstyle(ui, repo, *args, **opts): |
| '''check active C source files conform to the C Style Guide |
| |
| Check that any added or modified C source file conform to the C |
| Style Guide. |
| |
| See the C Style Guide for more information about correct C source |
| formatting. |
| (http://hub.opensolaris.org/bin/download/Community+Group+on/WebHome/cstyle.ms.pdf) |
| |
| Files can be excluded from this check using the cstyle.NOT file. |
| See NOT Files in the extension documentation ('hg help cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'cstyle') |
| ret = 0 |
| |
| ui.write('C style check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif not (f.endswith('.c') or f.endswith('.h')): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| |
| fh = open(f, 'r') |
| ret |= CStyle.cstyle(fh, output=ui, |
| picky=True, check_posix_types=True, |
| check_continuation=True) |
| fh.close() |
| return ret |
| |
| |
| def cdm_jstyle(ui, repo, *args, **opts): |
| '''check active Java source files for common stylistic errors |
| |
| Files can be excluded from this check using the jstyle.NOT file. |
| See NOT Files in the extension documentation ('hg help cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'jstyle') |
| ret = 0 |
| |
| ui.write('Java style check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif not f.endswith('.java'): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| |
| fh = open(f, 'r') |
| ret |= JStyle.jstyle(fh, output=ui, picky=True) |
| fh.close() |
| return ret |
| |
| |
| def cdm_permchk(ui, repo, *args, **opts): |
| '''check the permissions of each active file |
| |
| Check that the file permissions of each added or modified file do not |
| contain the executable bit. |
| |
| Files can be excluded from this check using the permchk.NOT file. |
| See NOT Files in the extension documentation ('hg help cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'permchk') |
| exeFiles = [] |
| |
| ui.write('File permission check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| |
| mode = stat.S_IMODE(os.stat(f)[stat.ST_MODE]) |
| if mode & stat.S_IEXEC: |
| exeFiles.append(f) |
| |
| if len(exeFiles) > 0: |
| ui.write('Warning: the following active file(s) have executable mode ' |
| '(+x) permission set,\nremove unless intentional:\n') |
| for fname in exeFiles: |
| ui.write(" %s\n" % fname) |
| |
| return len(exeFiles) > 0 |
| |
| |
| def cdm_tagchk(ui, repo, **opts): |
| '''check modification of workspace tags |
| |
| Check for any modification of the repository's .hgtags file. |
| |
| With the exception of the gatekeepers, nobody should introduce or |
| modify a repository's tags. |
| ''' |
| |
| active = wslist[repo].active(opts.get('parent')) |
| |
| ui.write('Checking for new tags:\n') |
| |
| if ".hgtags" in active: |
| tfile = wslist[repo].filepath('.hgtags') |
| ptip = active.parenttip.rev() |
| |
| ui.write('Warning: Workspace contains new non-local tags.\n' |
| 'Only gatekeepers should add or modify such tags.\n' |
| 'Use the following commands to revert these changes:\n' |
| ' hg revert -r%d %s\n' |
| ' hg commit %s\n' |
| 'You should also recommit before integration\n' % |
| (ptip, tfile, tfile)) |
| |
| return 1 |
| |
| return 0 |
| |
| |
| def cdm_branchchk(ui, repo, **opts): |
| '''check for changes in number or name of branches |
| |
| Check that the workspace contains only a single head, that it is |
| on the branch 'default', and that no new branches have been |
| introduced. |
| ''' |
| |
| ui.write('Checking for multiple heads (or branches):\n') |
| |
| heads = set(repo.heads()) |
| parents = set([x.node() for x in wslist[repo].workingctx().parents()]) |
| |
| # |
| # We care if there's more than one head, and those heads aren't |
| # identical to the dirstate parents (if they are identical, it's |
| # an uncommitted merge which mergechk will catch, no need to |
| # complain twice). |
| # |
| if len(heads) > 1 and heads != parents: |
| ui.write('Workspace has multiple heads (or branches):\n') |
| for head in [repo.changectx(head) for head in heads]: |
| ui.write(" %d:%s\t%s\n" % |
| (head.rev(), str(head), head.description().splitlines()[0])) |
| ui.write('You must merge and recommit.\n') |
| return 1 |
| |
| ui.write('\nChecking for branch changes:\n') |
| |
| if repo.dirstate.branch() != 'default': |
| ui.write("Warning: Workspace tip has named branch: '%s'\n" |
| "Only gatekeepers should push new branches.\n" |
| "Use the following commands to restore the branch name:\n" |
| " hg branch [-f] default\n" |
| " hg commit\n" |
| "You should also recommit before integration\n" % |
| (repo.dirstate.branch())) |
| return 1 |
| |
| branches = repo.branchtags().keys() |
| if len(branches) > 1: |
| ui.write('Warning: Workspace has named branches:\n') |
| for t in branches: |
| if t == 'default': |
| continue |
| ui.write("\t%s\n" % t) |
| |
| ui.write("Only gatekeepers should push new branches.\n" |
| "Use the following commands to remove extraneous branches.\n" |
| " hg branch [-f] default\n" |
| " hg commit" |
| "You should also recommit before integration\n") |
| return 1 |
| |
| return 0 |
| |
| |
| def cdm_keywords(ui, repo, *args, **opts): |
| '''check active files for SCCS keywords |
| |
| Check that any added or modified files do not contain SCCS keywords |
| (#ident lines, etc.). |
| |
| Files can be excluded from this check using the keywords.NOT file. |
| See NOT Files in the extension documentation ('hg help cdm'). |
| ''' |
| |
| filelist = buildfilelist(wslist[repo], opts.get('parent'), args) |
| exclude = not_check(repo, 'keywords') |
| ret = 0 |
| |
| ui.write('Keywords check:\n') |
| |
| for f, e in filelist: |
| if e and e.is_removed(): |
| continue |
| elif (e or opts.get('honour_nots')) and exclude(f): |
| ui.status('Skipping %s...\n' % f) |
| continue |
| |
| fh = open(f, 'r') |
| ret |= Keywords.keywords(fh, output=ui) |
| fh.close() |
| return ret |
| |
| |
| # |
| # NB: |
| # There's no reason to hook this up as an invokable command, since |
| # we have 'hg status', but it must accept the same arguments. |
| # |
| def cdm_outchk(ui, repo, **opts): |
| '''Warn the user if they have uncommitted changes''' |
| |
| ui.write('Checking for uncommitted changes:\n') |
| |
| st = wslist[repo].modified() |
| if st: |
| ui.write('Warning: the following files have uncommitted changes:\n') |
| for elt in st: |
| ui.write(' %s\n' % elt) |
| return 1 |
| return 0 |
| |
| |
| def cdm_mergechk(ui, repo, **opts): |
| '''Warn the user if their workspace contains merges''' |
| |
| active = wslist[repo].active(opts.get('parent')) |
| |
| ui.write('Checking for merges:\n') |
| |
| merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1], |
| active.revs) |
| |
| if merges: |
| ui.write('Workspace contains the following merges:\n') |
| for rev in merges: |
| desc = rev.description().splitlines() |
| ui.write(' %s:%s\t%s\n' % |
| (rev.rev() or "working", str(rev), |
| desc and desc[0] or "*** uncommitted change ***")) |
| return 1 |
| return 0 |
| |
| |
| def run_checks(ws, cmds, *args, **opts): |
| '''Run CMDS (with OPTS) over active files in WS''' |
| |
| ret = 0 |
| |
| for cmd in cmds: |
| name = cmd.func_name.split('_')[1] |
| if not ws.ui.configbool('cdm', name, True): |
| ws.ui.status('Skipping %s check...\n' % name) |
| else: |
| ws.ui.pushbuffer() |
| result = cmd(ws.ui, ws.repo, honour_nots=True, *args, **opts) |
| output = ws.ui.popbuffer() |
| |
| ret |= result |
| |
| if not ws.ui.quiet or result != 0: |
| ws.ui.write(output, '\n') |
| return ret |
| |
| |
| def cdm_nits(ui, repo, *args, **opts): |
| '''check for stylistic nits in active files |
| |
| Check each active file for basic stylistic errors. |
| |
| The following checks are run over each active file (see 'hg help |
| <check>' for more information about each): |
| |
| - copyright (copyright statements) |
| - cstyle (C source style) |
| - hdrchk (C header style) |
| - jstyle (java source style) |
| - manlint (man page formatting) |
| - mapfilechk (link-editor mapfiles) |
| - permchk (file permissions) |
| - keywords (SCCS keywords) |
| |
| With the global -q/--quiet option, only provide output for those |
| checks which fail. |
| ''' |
| |
| cmds = [cdm_copyright, |
| cdm_cstyle, |
| cdm_hdrchk, |
| cdm_jstyle, |
| cmd_manlintchk, |
| cdm_mapfilechk, |
| cdm_permchk, |
| cdm_keywords] |
| |
| return run_checks(wslist[repo], cmds, *args, **opts) |
| |
| |
| def cdm_pbchk(ui, repo, **opts): |
| '''run pre-integration checks on this workspace |
| |
| Check this workspace for common errors prior to integration. |
| |
| The following checks are run over the active list (see 'hg help |
| <check>' for more information about each): |
| |
| - branchchk (addition/modification of branches) |
| - comchk (changeset descriptions) |
| - copyright (copyright statements) |
| - cstyle (C source style) |
| - hdrchk (C header style) |
| - jstyle (java source style) |
| - keywords (SCCS keywords) |
| - manlint (man page formatting) |
| - mapfilechk (link-editor mapfiles) |
| - permchk (file permissions) |
| - tagchk (addition/modification of tags) |
| |
| Additionally, the workspace is checked for outgoing merges (which |
| should be removed with 'hg recommit'), and uncommitted changes. |
| |
| With the global -q/--quiet option, only provide output for those |
| checks which fail. |
| ''' |
| |
| # |
| # The current ordering of these is that the commands from cdm_nits |
| # run first in the same order as they would in cdm_nits, then the |
| # pbchk specifics are run. |
| # |
| cmds = [cdm_copyright, |
| cdm_cstyle, |
| cdm_hdrchk, |
| cdm_jstyle, |
| cdm_manlintchk, |
| cdm_mapfilechk, |
| cdm_permchk, |
| cdm_keywords, |
| cdm_comchk, |
| cdm_tagchk, |
| cdm_branchchk, |
| cdm_outchk, |
| cdm_mergechk] |
| |
| return run_checks(wslist[repo], cmds, **opts) |
| |
| |
| def cdm_recommit(ui, repo, **opts): |
| '''replace outgoing changesets with a single equivalent changeset |
| |
| Replace all outgoing changesets with a single changeset containing |
| equivalent changes. This removes uninteresting changesets created |
| during development that would only serve as noise in the gate. |
| |
| Any changed file that is now identical in content to that in the |
| parent workspace (whether identical in history or otherwise) will |
| not be included in the new changeset. Any merges information will |
| also be removed. |
| |
| If no files are changed in comparison to the parent workspace, the |
| outgoing changesets will be removed, but no new changeset created. |
| |
| recommit will refuse to run if the workspace contains more than |
| one outgoing head, even if those heads are on the same branch. To |
| recommit with only one branch containing outgoing changesets, your |
| workspace must be on that branch and at that branch head. |
| |
| recommit will prompt you to take a backup if your workspace has |
| been changed since the last backup was taken. In almost all |
| cases, you should allow it to take one (the default). |
| |
| recommit cannot be run if the workspace contains any uncommitted |
| changes, applied Mq patches, or has multiple outgoing heads (or |
| branches). |
| ''' |
| |
| ws = wslist[repo] |
| |
| if not os.getcwd().startswith(repo.root): |
| raise util.Abort('recommit is not safe to run with -R') |
| |
| abort_if_dirty(ws) |
| |
| wlock = repo.wlock() |
| lock = repo.lock() |
| |
| try: |
| parent = ws.parent(opts['parent']) |
| between = repo.changelog.nodesbetween(ws.findoutgoing(parent))[2] |
| heads = set(between) & set(repo.heads()) |
| |
| if len(heads) > 1: |
| ui.warn('Workspace has multiple outgoing heads (or branches):\n') |
| for head in sorted(map(repo.changelog.rev, heads), reverse=True): |
| ui.warn('\t%d\n' % head) |
| raise util.Abort('you must merge before recommitting') |
| |
| # |
| # We can safely use the worklist here, as we know (from the |
| # abort_if_dirty() check above) that the working copy has not been |
| # modified. |
| # |
| active = ws.active(parent) |
| |
| if filter(lambda b: len(b.parents()) > 1, active.bases()): |
| raise util.Abort('Cannot recommit a merge of two non-outgoing ' |
| 'changesets') |
| |
| if len(active.revs) <= 0: |
| raise util.Abort("no changes to recommit") |
| |
| if len(active.files()) <= 0: |
| ui.warn("Recommitting %d active changesets, but no active files\n" % |
| len(active.revs)) |
| |
| # |
| # During the course of a recommit, any file bearing a name |
| # matching the source name of any renamed file will be |
| # clobbered by the operation. |
| # |
| # As such, we ask the user before proceeding. |
| # |
| bogosity = [f.parentname for f in active if f.is_renamed() and |
| os.path.exists(repo.wjoin(f.parentname))] |
| if bogosity: |
| ui.warn("The following file names are the original name of a " |
| "rename and also present\n" |
| "in the working directory:\n") |
| |
| for fname in bogosity: |
| ui.warn(" %s\n" % fname) |
| |
| if not yes_no(ui, "These files will be removed by recommit." |
| " Continue?", |
| False): |
| raise util.Abort("recommit would clobber files") |
| |
| user = opts['user'] or ui.username() |
| comments = '\n'.join(active.comments()) |
| |
| message = cmdutil.logmessage(opts) or ui.edit(comments, user) |
| if not message: |
| raise util.Abort('empty commit message') |
| |
| bk = CdmBackup(ui, ws, backup_name(repo.root)) |
| if bk.need_backup(): |
| if yes_no(ui, 'Do you want to backup files first?', True): |
| bk.backup() |
| |
| oldtags = repo.tags() |
| clearedtags = [(name, nd, repo.changelog.rev(nd), local) |
| for name, nd, local in active.tags()] |
| |
| ws.squishdeltas(active, message, user=user) |
| finally: |
| lock.release() |
| wlock.release() |
| |
| if clearedtags: |
| ui.write("Removed tags:\n") |
| for name, nd, rev, local in sorted(clearedtags, |
| key=lambda x: x[0].lower()): |
| ui.write(" %5s:%s:\t%s%s\n" % (rev, node.short(nd), |
| name, (local and ' (local)' or ''))) |
| |
| for ntag, nnode in sorted(repo.tags().items(), |
| key=lambda x: x[0].lower()): |
| if ntag in oldtags and ntag != "tip": |
| if oldtags[ntag] != nnode: |
| ui.write("tag '%s' now refers to revision %d:%s\n" % |
| (ntag, repo.changelog.rev(nnode), |
| node.short(nnode))) |
| |
| |
| def do_eval(cmd, files, root, changedir=True): |
| if not changedir: |
| os.chdir(root) |
| |
| for path in sorted(files): |
| dirn, base = os.path.split(path) |
| |
| if changedir: |
| os.chdir(os.path.join(root, dirn)) |
| |
| os.putenv('workspace', root) |
| os.putenv('filepath', path) |
| os.putenv('dir', dirn) |
| os.putenv('file', base) |
| os.system(cmd) |
| |
| |
| def cdm_eval(ui, repo, *command, **opts): |
| '''run specified command for each active file |
| |
| Run the command specified on the command line for each active |
| file, with the following variables present in the environment: |
| |
| :$file: - active file basename. |
| :$dir: - active file dirname. |
| :$filepath: - path from workspace root to active file. |
| :$workspace: - full path to workspace root. |
| |
| For example: |
| |
| hg eval 'echo $dir; hg log -l3 $file' |
| |
| will show the last the 3 log entries for each active file, |
| preceded by its directory. |
| ''' |
| |
| act = wslist[repo].active(opts['parent']) |
| cmd = ' '.join(command) |
| files = [x.name for x in act if not x.is_removed()] |
| |
| do_eval(cmd, files, repo.root, not opts['remain']) |
| |
| |
| def cdm_apply(ui, repo, *command, **opts): |
| '''apply specified command to all active files |
| |
| Run the command specified on the command line over each active |
| file. |
| |
| For example 'hg apply "wc -l"' will output a count of the lines in |
| each active file. |
| ''' |
| |
| act = wslist[repo].active(opts['parent']) |
| |
| if opts['remain']: |
| appnd = ' $filepath' |
| else: |
| appnd = ' $file' |
| |
| cmd = ' '.join(command) + appnd |
| files = [x.name for x in act if not x.is_removed()] |
| |
| do_eval(cmd, files, repo.root, not opts['remain']) |
| |
| |
| def cdm_reparent(ui, repo, parent): |
| '''reparent your workspace |
| |
| Update the 'default' path alias that is used as the default source |
| for 'hg pull' and the default destination for 'hg push' (unless |
| there is a 'default-push' alias). This is also the path all |
| Cadmium commands treat as your parent workspace. |
| ''' |
| |
| def append_new_parent(parent): |
| fp = None |
| try: |
| fp = repo.opener('hgrc', 'a', atomictemp=True) |
| if fp.tell() != 0: |
| fp.write('\n') |
| fp.write('[paths]\n' |
| 'default = %s\n\n' % parent) |
| fp.rename() |
| finally: |
| if fp and not fp.closed: |
| fp.close() |
| |
| def update_parent(path, line, parent): |
| line = line - 1 # The line number we're passed will be 1-based |
| fp = None |
| |
| try: |
| fp = open(path) |
| data = fp.readlines() |
| finally: |
| if fp and not fp.closed: |
| fp.close() |
| |
| # |
| # line will be the last line of any continued block, go back |
| # to the first removing the continuation as we go. |
| # |
| while data[line][0].isspace(): |
| data.pop(line) |
| line -= 1 |
| |
| assert data[line].startswith('default') |
| |
| data[line] = "default = %s\n" % parent |
| if data[-1] != '\n': |
| data.append('\n') |
| |
| try: |
| fp = util.atomictempfile(path, 'w', 0644) |
| fp.writelines(data) |
| fp.rename() |
| finally: |
| if fp and not fp.closed: |
| fp.close() |
| |
| from mercurial import config |
| parent = ui.expandpath(parent) |
| |
| if not os.path.exists(repo.join('hgrc')): |
| append_new_parent(parent) |
| return |
| |
| cfg = config.config() |
| cfg.read(repo.join('hgrc')) |
| source = cfg.source('paths', 'default') |
| |
| if not source: |
| append_new_parent(parent) |
| return |
| else: |
| path, target = source.rsplit(':', 1) |
| |
| if path != repo.join('hgrc'): |
| raise util.Abort("Cannot edit path specification not in repo hgrc\n" |
| "default path is from: %s" % source) |
| |
| update_parent(path, int(target), parent) |
| |
| |
| def backup_name(fullpath): |
| '''Create a backup directory name based on the specified path. |
| |
| In most cases this is the basename of the path specified, but |
| certain cases are handled specially to create meaningful names''' |
| |
| special = ['usr/closed'] |
| |
| fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep) |
| |
| # |
| # If a path is 'special', we append the basename of the path to |
| # the path element preceding the constant, special, part. |
| # |
| # Such that for instance: |
| # /foo/bar/onnv-fixes/usr/closed |
| # has a backup name of: |
| # onnv-fixes-closed |
| # |
| for elt in special: |
| elt = elt.split(os.path.sep) |
| pathpos = len(elt) |
| |
| if fullpath[-pathpos:] == elt: |
| return "%s-%s" % (fullpath[-pathpos - 1], elt[-1]) |
| else: |
| return fullpath[-1] |
| |
| |
| def cdm_backup(ui, repo, if_newer=False): |
| '''backup workspace changes and metadata |
| |
| Create a backup copy of changes made in this workspace as compared |
| to its parent workspace, as well as important metadata of this |
| workspace. |
| |
| NOTE: Only changes as compared to the parent workspace are backed |
| up. If you lose this workspace and its parent, you will not be |
| able to restore a backup into a clone of the grandparent |
| workspace. |
| |
| By default, backups are stored in the cdm.backup/ directory in |
| your home directory. This is configurable using the cdm.backupdir |
| configuration variable, for example: |
| |
| hg backup --config cdm.backupdir=/net/foo/backups |
| |
| or place the following in an appropriate hgrc file:: |
| |
| [cdm] |
| backupdir = /net/foo/backups |
| |
| Backups have the same name as the workspace in which they were |
| taken, with '-closed' appended in the case of O/N's usr/closed. |
| ''' |
| |
| name = backup_name(repo.root) |
| bk = CdmBackup(ui, wslist[repo], name) |
| |
| wlock = repo.wlock() |
| lock = repo.lock() |
| |
| try: |
| if if_newer and not bk.need_backup(): |
| ui.status('backup is up-to-date\n') |
| else: |
| bk.backup() |
| finally: |
| lock.release() |
| wlock.release() |
| |
| |
| def cdm_restore(ui, repo, backup, **opts): |
| '''restore workspace from backup |
| |
| Restore this workspace from a backup (taken by 'hg backup'). |
| |
| If the specified backup directory does not exist, it is assumed to |
| be relative to the cadmium backup directory (~/cdm.backup/ by |
| default). |
| |
| For example:: |
| |
| % hg restore on-rfe - Restore the latest backup of ~/cdm.backup/on-rfe |
| % hg restore -g3 on-rfe - Restore the 3rd backup of ~/cdm.backup/on-rfe |
| % hg restore /net/foo/backup/on-rfe - Restore from an explicit path |
| ''' |
| |
| if not os.getcwd().startswith(repo.root): |
| raise util.Abort('restore is not safe to run with -R') |
| |
| abort_if_dirty(wslist[repo]) |
| |
| if opts['generation']: |
| gen = int(opts['generation']) |
| else: |
| gen = None |
| |
| if os.path.exists(backup): |
| backup = os.path.abspath(backup) |
| |
| wlock = repo.wlock() |
| lock = repo.lock() |
| |
| try: |
| bk = CdmBackup(ui, wslist[repo], backup) |
| bk.restore(gen) |
| finally: |
| lock.release() |
| wlock.release() |
| |
| |
| def cdm_webrev(ui, repo, **opts): |
| '''generate web-based code review and optionally upload it |
| |
| Generate a web-based code review using webrev(1) and optionally |
| upload it. All known arguments are passed through to webrev(1). |
| ''' |
| |
| webrev_args = "" |
| for key in opts.keys(): |
| if opts[key]: |
| if type(opts[key]) == type(True): |
| webrev_args += '-' + key + ' ' |
| else: |
| webrev_args += '-' + key + ' ' + opts[key] + ' ' |
| |
| retval = os.system('webrev ' + webrev_args) |
| if retval != 0: |
| return retval - 255 |
| |
| return 0 |
| |
| |
| def cdm_debugcdmal(ui, repo, *pats, **opts): |
| '''dump the active list for the sake of debugging/testing''' |
| |
| ui.write(wslist[repo].active(opts['parent']).as_text(pats)) |
| |
| |
| def cdm_changed(ui, repo, *pats, **opts): |
| '''mark a file as changed in the working copy |
| |
| Maintain a list of files checked for modification in the working |
| copy. If the list exists, most cadmium commands will only check |
| the working copy for changes to those files, rather than checking |
| the whole workspace (this does not apply to committed changes, |
| which are always seen). |
| |
| Since this list functions only as a hint as to where in the |
| working copy to look for changes, entries that have not actually |
| been modified (in the working copy, or in general) are not |
| problematic. |
| |
| |
| Note: If such a list exists, it must be kept up-to-date. |
| |
| |
| Renamed files can be added with reference only to their new name: |
| $ hg mv foo bar |
| $ hg changed bar |
| |
| Without arguments, 'hg changed' will list all files recorded as |
| altered, such that, for instance: |
| $ hg status $(hg changed) |
| $ hg diff $(hg changed) |
| Become useful (generally faster than their unadorned counterparts) |
| |
| To create an initially empty list: |
| $ hg changed -i |
| Until files are added to the list it is equivalent to saying |
| "Nothing has been changed" |
| |
| Update the list based on the current active list: |
| $ hg changed -u |
| The old list is emptied, and replaced with paths from the |
| current active list. |
| |
| Remove the list entirely: |
| $ hg changed -d |
| ''' |
| |
| def modded_files(repo, parent): |
| out = wslist[repo].findoutgoing(wslist[repo].parent(parent)) |
| outnodes = repo.changelog.nodesbetween(out)[0] |
| |
| files = set() |
| for n in outnodes: |
| files.update(repo.changectx(n).files()) |
| |
| files.update(wslist[repo].status().keys()) |
| return files |
| |
| # |
| # specced_pats is convenient to treat as a boolean indicating |
| # whether any file patterns or paths were specified. |
| # |
| specced_pats = pats or opts['include'] or opts['exclude'] |
| if len(filter(None, [opts['delete'], opts['update'], opts['init'], |
| specced_pats])) > 1: |
| raise util.Abort("-d, -u, -i and patterns are mutually exclusive") |
| |
| wl = WorkList(wslist[repo]) |
| |
| if (not wl and specced_pats) or opts['init']: |
| wl.delete() |
| if yes_no(ui, "Create a list based on your changes thus far?", True): |
| map(wl.add, modded_files(repo, opts.get('parent'))) |
| |
| if opts['delete']: |
| wl.delete() |
| elif opts['update']: |
| wl.delete() |
| map(wl.add, modded_files(repo, opts.get('parent'))) |
| wl.write() |
| elif opts['init']: # Any possible old list was deleted above |
| wl.write() |
| elif specced_pats: |
| sources = [] |
| |
| match = wslist[repo].matcher(pats=pats, opts=opts) |
| for abso in repo.walk(match): |
| if abso in repo.dirstate: |
| wl.add(abso) |
| # |
| # Store the source name of any copy. We use this so |
| # both the add and delete of a rename can be entered |
| # into the WorkList with only the destination name |
| # explicitly being mentioned. |
| # |
| fctx = wslist[repo].workingctx().filectx(abso) |
| rn = fctx.renamed() |
| if rn: |
| sources.append(rn[0]) |
| else: |
| ui.warn("%s is not version controlled -- skipping\n" % |
| match.rel(abso)) |
| |
| if sources: |
| for fname, chng in wslist[repo].status(files=sources).iteritems(): |
| if chng == 'removed': |
| wl.add(fname) |
| wl.write() |
| else: |
| for elt in sorted(wl.list()): |
| ui.write("%s\n" % wslist[repo].filepath(elt)) |
| |
| |
| cmdtable = { |
| 'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'), |
| ('r', 'remain', None, 'do not change directory')], |
| 'hg apply [-p PARENT] [-r] command...'), |
| '^backup|bu': (cdm_backup, [('t', 'if-newer', None, |
| 'only backup if workspace files are newer')], |
| 'hg backup [-t]'), |
| 'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')], |
| 'hg branchchk [-p PARENT]'), |
| 'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')], |
| 'hg bugs [-p PARENT]'), |
| 'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')], |
| 'hg cddlchk [-p PARENT]'), |
| 'changed': (cdm_changed, [('d', 'delete', None, 'delete the file list'), |
| ('u', 'update', None, 'mark all changed files'), |
| ('i', 'init', None, 'create an empty file list'), |
| ('p', 'parent', '', 'parent workspace'), |
| ('I', 'include', [], |
| 'include names matching the given patterns'), |
| ('X', 'exclude', [], |
| 'exclude names matching the given patterns')], |
| 'hg changed -d\n' |
| 'hg changed -u\n' |
| 'hg changed -i\n' |
| 'hg changed [-I PATTERN...] [-X PATTERN...] [FILE...]'), |
| 'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'), |
| ('N', 'nocheck', None, |
| 'do not compare comments with databases')], |
| 'hg comchk [-p PARENT]'), |
| 'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')], |
| 'hg comments [-p PARENT]'), |
| 'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')], |
| 'hg copyright [-p PARENT]'), |
| 'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')], |
| 'hg cstyle [-p PARENT]'), |
| 'debugcdmal': (cdm_debugcdmal, [('p', 'parent', '', 'parent workspace')], |
| 'hg debugcdmal [-p PARENT] [FILE...]'), |
| 'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'), |
| ('r', 'remain', None, 'do not change directory')], |
| 'hg eval [-p PARENT] [-r] command...'), |
| 'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')], |
| 'hg hdrchk [-p PARENT]'), |
| 'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')], |
| 'hg jstyle [-p PARENT]'), |
| 'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')], |
| 'hg keywords [-p PARENT]'), |
| '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'), |
| ('a', 'added', None, 'show added files'), |
| ('m', 'modified', None, 'show modified files'), |
| ('r', 'removed', None, 'show removed files')], |
| 'hg list [-amrRu] [-p PARENT]'), |
| 'manlint': (cdm_manlintchk, [('p', 'parent', '', 'parent workspace')], |
| 'hg manlint [-p PARENT]'), |
| 'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')], |
| 'hg mapfilechk [-p PARENT]'), |
| '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')], |
| 'hg nits [-p PARENT]'), |
| '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'), |
| ('N', 'nocheck', None, 'skip database checks')], |
| 'hg pbchk [-N] [-p PARENT]'), |
| 'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')], |
| 'hg permchk [-p PARENT]'), |
| '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace'), |
| ('a', 'text', None, 'treat all files as text'), |
| ('g', 'git', None, 'use extended git diff format'), |
| ('w', 'ignore-all-space', None, |
| 'ignore white space when comparing lines'), |
| ('b', 'ignore-space-change', None, |
| 'ignore changes in the amount of white space'), |
| ('B', 'ignore-blank-lines', None, |
| 'ignore changes whose lines are all blank'), |
| ('U', 'unified', 3, |
| 'number of lines of context to show'), |
| ('I', 'include', [], |
| 'include names matching the given patterns'), |
| ('X', 'exclude', [], |
| 'exclude names matching the given patterns')], |
| 'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'), |
| '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'), |
| ('m', 'message', '', |
| 'use <text> as commit message'), |
| ('l', 'logfile', '', |
| 'read commit message from file'), |
| ('u', 'user', '', |
| 'record user as committer')], |
| 'hg recommit [-m TEXT] [-l FILE] [-u USER] [-p PARENT]'), |
| 'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')], |
| 'hg renamed [-p PARENT]'), |
| 'reparent': (cdm_reparent, [], 'hg reparent PARENT'), |
| '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')], |
| 'hg restore [-g GENERATION] BACKUP'), |
| 'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')], |
| 'hg tagchk [-p PARENT]'), |
| 'webrev': (cdm_webrev, [('C', 'C', '', 'ITS priority file'), |
| ('D', 'D', '', 'delete remote webrev'), |
| ('I', 'I', '', 'ITS configuration file'), |
| ('i', 'i', '', 'include file'), |
| ('N', 'N', None, 'suppress comments'), |
| ('n', 'n', None, 'do not generate webrev'), |
| ('O', 'O', None, 'OpenSolaris mode'), |
| ('o', 'o', '', 'output directory'), |
| ('p', 'p', '', 'use specified parent'), |
| ('t', 't', '', 'upload target'), |
| ('U', 'U', None, 'upload the webrev'), |
| ('w', 'w', '', 'use wx active file')], |
| 'hg webrev [WEBREV_OPTIONS]'), |
| } |