changeset 13204:0913a3a45383

259 SCM tools should work with wider range of Mercurial versions Reviewed by: garrett@nexenta.com Approved by: gwr@nexenta.com
author Richard Lowe <richlowe@richlowe.net>
date Fri, 08 Oct 2010 22:44:45 -0400
parents d46bf23d63d5
children 969b97e84bdc
files usr/src/tools/onbld/Scm/Backup.py usr/src/tools/onbld/Scm/Version.py usr/src/tools/onbld/Scm/WorkSpace.py usr/src/tools/onbld/hgext/cdm.py usr/src/tools/scripts/hg-active.py
diffstat 5 files changed, 421 insertions(+), 190 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/tools/onbld/Scm/Backup.py	Sat Oct 09 16:16:14 2010 -0500
+++ b/usr/src/tools/onbld/Scm/Backup.py	Fri Oct 08 22:44:45 2010 -0400
@@ -17,6 +17,8 @@
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
+# Copyright 2008, 2010, Richard Lowe
+#
 
 '''
 Workspace backup
@@ -58,10 +60,7 @@
 '''
 
 import os, pwd, shutil, tarfile, time, traceback
-from mercurial import changegroup, node, patch, util
-
-from onbld.Scm.WorkSpace import HgLookupError
-import onbld.Scm.Version as Version
+from mercurial import changegroup, error, node, patch, util
 
 
 class CdmNodeMissing(util.Abort):
@@ -158,7 +157,7 @@
                 except EnvironmentError, e:
                     raise util.Abort("couldn't restore committed changes: %s\n"
                                      "   %s" % (bfile, e))
-                except HgLookupError, e:
+                except error.LookupError, e:
                     raise CdmNodeMissing("couldn't restore committed changes",
                                                      e.name)
             finally:
@@ -290,7 +289,7 @@
         try:
             n = node.bin(dirstate)
             self.ws.repo.changelog.lookup(n)
-        except HgLookupError, e:
+        except error.LookupError, e:
             raise CdmNodeMissing("couldn't restore uncommitted changes",
                                  e.name)
 
@@ -339,7 +338,7 @@
             fp = open(self.bu.backupfile('renames'))
             for line in fp:
                 source, dest = line.strip().split()
-                self.ws.repo.copy(source, dest)
+                self.ws.copy(source, dest)
         except EnvironmentError, e:
             raise util.Abort('unable to open renames file: %s' % e)
         except ValueError:
@@ -434,14 +433,14 @@
                 # in question, so we have to do so ourselves.
                 #
                 if isinstance(e, tarfile.TarError):
-                    error = "%s: %s" % (elt, e)
+                    errstr = "%s: %s" % (elt, e)
                 else:
-                    error = str(e)
+                    errstr = str(e)
 
                 raise util.Abort("couldn't backup metadata to %s:\n"
                                  "  %s" %
                                  (self.bu.backupfile('metadata.tar.gz'),
-                                  error))
+                                  errstr))
         finally:
             if tar and not tar.closed:
                 tar.close()
@@ -480,14 +479,14 @@
                 except (EnvironmentError, tarfile.TarError), e:
                     # Make sure the member name is in the exception message.
                     if isinstance(e, tarfile.TarError):
-                        error = "%s: %s" % (elt.name, e)
+                        errstr = "%s: %s" % (elt.name, e)
                     else:
-                        error = str(e)
+                        errstr = str(e)
 
                     raise util.Abort("couldn't restore metadata from %s:\n"
                                      "   %s" %
                                      (self.bu.backupfile('metadata.tar.gz'),
-                                      error))
+                                      errstr))
             finally:
                 if tar and not tar.closed:
                     tar.close()
@@ -694,11 +693,8 @@
                 self.ws.ui.warn("Interrupted\n")
             else:
                 self.ws.ui.warn("Error: %s\n" % e)
-                if Version.at_least("1.3.0"):
-                    show_traceback = self.ws.ui.configbool('ui', 'traceback',
-                                                   False)
-                else:
-                    show_traceback = self.ws.ui.traceback
+                show_traceback = self.ws.ui.configbool('ui', 'traceback',
+                                                       False)
 
                 #
                 # If it's not a 'normal' error, we want to print a stack
--- a/usr/src/tools/onbld/Scm/Version.py	Sat Oct 09 16:16:14 2010 -0500
+++ b/usr/src/tools/onbld/Scm/Version.py	Fri Oct 08 22:44:45 2010 -0400
@@ -17,6 +17,8 @@
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
+# Copyright 2008, 2010, Richard Lowe
+#
 
 '''
 Deal with Mercurial versioning.
@@ -37,6 +39,10 @@
 # via ImportError to account for mercurial.demandimport delaying the
 # ImportError exception.
 #
+# This code needs to remain, even though versions prior to 1.2 aren't
+# supported, to allow us to produce the error message which states that they
+# are not supported.
+#
 from mercurial import util
 if hasattr(util, 'version'):
     hg_version = util.version
@@ -52,7 +58,7 @@
 #
 # List of versions that are explicitly acceptable to us
 #
-GOOD_VERSIONS = ['1.1.2', '1.3.1']
+GOOD_VERSIONS = ['1.3.1', '1.4.2', '1.5.4', '1.6.2', '1.6.3']
 
 
 def check_version():
--- a/usr/src/tools/onbld/Scm/WorkSpace.py	Sat Oct 09 16:16:14 2010 -0500
+++ b/usr/src/tools/onbld/Scm/WorkSpace.py	Fri Oct 08 22:44:45 2010 -0400
@@ -15,6 +15,7 @@
 
 #
 # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2008, 2010, Richard Lowe
 #
 
 #
@@ -44,23 +45,16 @@
 
 import cStringIO
 import os
-from mercurial import cmdutil, context, hg, node, patch, repair, util
+from mercurial import cmdutil, context, error, hg, node, patch, repair, util
 from hgext import mq
 
 from onbld.Scm import Version
 
 #
-# Mercurial >= 1.2 has its exception types in a mercurial.error
-# module, prior versions had them in their associated modules.
+# Mercurial 1.6 moves findoutgoing into a discover module
 #
-if Version.at_least("1.2"):
-    from mercurial import error
-    HgRepoError = error.RepoError
-    HgLookupError = error.LookupError
-else:
-    from mercurial import repo, revlog
-    HgRepoError = repo.RepoError
-    HgLookupError = revlog.LookupError
+if Version.at_least("1.6"):
+    from mercurial import discovery
 
 
 class ActiveEntry(object):
@@ -200,7 +194,7 @@
 
                 try:
                     fctx = ctx.filectx(fname)
-                except HgLookupError:
+                except error.LookupError:
                     continue
 
                 #
@@ -373,9 +367,7 @@
         The fast path compares file metadata, slow path is a
         real comparison of file content.'''
 
-        # Note that we use localtip.manifest() here because of a bug in
-        # Mercurial 1.1.2's workingctx.__contains__
-        if ((path in self.parenttip) != (path in self.localtip.manifest())):
+        if ((path in self.parenttip) != (path in self.localtip)):
             return True
 
         parentfile = self.parenttip.filectx(path)
@@ -515,42 +507,27 @@
         '''
 
         def tipmost_shared(head, outnodes):
-            '''Return the tipmost node on the same branch as head that is not
-            in outnodes.
+            '''Return the changeset on the same branch as head that is
+            not in outnodes and is closest to the tip.
 
-            We walk from head to the bottom of the workspace (revision
-            0) collecting nodes not in outnodes during the add phase
-            and return the first node we see in the iter phase that
-            was previously collected.
+            Walk outgoing changesets from head to the bottom of the
+            workspace (revision 0) and return the the first changeset
+            we see that is not in outnodes.
 
-            If no node is found (all revisions >= 0 are outgoing), the
+            If none is found (all revisions >= 0 are outgoing), the
             only possible parenttip is the null node (node.nullid)
             which is returned explicitly.
-
-            See the docstring of mercurial.cmdutil.walkchangerevs()
-            for the phased approach to the iterator returned.  The
-            important part to note is that the 'add' phase gathers
-            nodes, which the 'iter' phase then iterates through.'''
+            '''
+            for ctx in self._walkctxs(head, self.repo.changectx(0),
+                                      follow=True,
+                                      pick=lambda c: c.node() not in outnodes):
+                return ctx
 
-            opts = {'rev': ['%s:0' % head.rev()],
-                    'follow': True}
-            get = util.cachefunc(lambda r: self.repo.changectx(r).changeset())
-            changeiter = cmdutil.walkchangerevs(self.repo.ui, self.repo, [],
-                                                get, opts)[0]
-            seen = []
-            for st, rev, fns in changeiter:
-                n = self.repo.changelog.node(rev)
-                if st == 'add':
-                    if n not in outnodes:
-                        seen.append(n)
-                elif st == 'iter':
-                    if n in seen:
-                        return rev
-            return self.repo.changelog.rev(node.nullid)
+            return self.repo.changectx(node.nullid)
 
         nodes = set(outgoing)
         ptips = map(lambda x: tipmost_shared(x, nodes), heads)
-        return self.repo.changectx(sorted(ptips)[-1])
+        return sorted(ptips, key=lambda x: x.rev(), reverse=True)[0]
 
     def status(self, base='.', head=None):
         '''Translate from the hg 6-tuple status format to a hash keyed
@@ -576,8 +553,11 @@
                 if hasattr(cmdutil, 'remoteui'):
                     ui = cmdutil.remoteui(ui, {})
                 pws = hg.repository(ui, parent)
-                return self.repo.findoutgoing(pws)
-            except HgRepoError:
+                if Version.at_least("1.6"):
+                    return discovery.findoutgoing(self.repo, pws)
+                else:
+                    return self.repo.findoutgoing(pws)
+            except error.RepoError:
                 self.ui.warn("Warning: Parent workspace '%s' is not "
                              "accessible\n"
                              "active list will be incomplete\n\n" % parent)
@@ -607,8 +587,8 @@
     def active(self, parent=None):
         '''Return an ActiveList describing changes between workspace
         and parent workspace (including uncommitted changes).
-        If workspace has no parent ActiveList will still describe any
-        uncommitted changes'''
+        If workspace has no parent, ActiveList will still describe any
+        uncommitted changes.'''
 
         parent = self.parent(parent)
         if parent in self.activecache:
@@ -825,3 +805,78 @@
                 self.repo.dirstate.invalidate()
 
         return ret.getvalue()
+
+    if Version.at_least("1.6"):
+        def copy(self, src, dest):
+            '''Copy a file from src to dest
+            '''
+
+            self.workingctx().copy(src, dest)
+    else:
+        def copy(self, src, dest):
+            '''Copy a file from src to dest
+            '''
+
+            self.repo.copy(src, dest)
+
+
+    if Version.at_least("1.4"):
+
+        def _walkctxs(self, base, head, follow=False, pick=None):
+            '''Generate changectxs between BASE and HEAD.
+
+            Walk changesets between BASE and HEAD (in the order implied by
+            their relation), following a given branch if FOLLOW is a true
+            value, yielding changectxs where PICK (if specified) returns a
+            true value.
+
+            PICK is a function of one argument, a changectx.'''
+
+            chosen = {}
+
+            def prep(ctx, fns):
+                chosen[ctx.rev()] = not pick or pick(ctx)
+
+            opts = {'rev': ['%s:%s' % (base.rev(), head.rev())],
+                    'follow': follow}
+            matcher = cmdutil.matchall(self.repo)
+
+            for ctx in cmdutil.walkchangerevs(self.repo, matcher, opts, prep):
+                if chosen[ctx.rev()]:
+                    yield ctx
+    else:
+
+        def _walkctxs(self, base, head, follow=False, pick=None):
+            '''Generate changectxs between BASE and HEAD.
+
+            Walk changesets between BASE and HEAD (in the order implied by
+            their relation), following a given branch if FOLLOW is a true
+            value, yielding changectxs where PICK (if specified) returns a
+            true value.
+
+            PICK is a function of one argument, a changectx.'''
+
+            opts = {'rev': ['%s:%s' % (base.rev(), head.rev())],
+                    'follow': follow}
+
+            changectx = self.repo.changectx
+            getcset = util.cachefunc(lambda r: changectx(r).changeset())
+
+            #
+            # See the docstring of mercurial.cmdutil.walkchangerevs() for
+            # the phased approach to the iterator returned.  The important
+            # part to note is that the 'add' phase gathers nodes, which
+            # the 'iter' phase then iterates through.
+            #
+            changeiter = cmdutil.walkchangerevs(self.ui, self.repo,
+                                                [], getcset, opts)[0]
+
+            matched = {}
+            for st, rev, fns in changeiter:
+                if st == 'add':
+                    ctx = changectx(rev)
+                    if not pick or pick(ctx):
+                        matched[rev] = ctx
+                elif st == 'iter':
+                    if rev in matched:
+                        yield matched[rev]
--- a/usr/src/tools/onbld/hgext/cdm.py	Sat Oct 09 16:16:14 2010 -0500
+++ b/usr/src/tools/onbld/hgext/cdm.py	Fri Oct 08 22:44:45 2010 -0400
@@ -15,24 +15,65 @@
 
 #
 # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2008, 2010 Richard Lowe
 #
 
-# Copyright 2008, 2010, Richard Lowe
+'''OpenSolaris extensions to Mercurial
 
-'''OpenSolaris workspace extensions for 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.
 
-This extension contains a number of commands to help you work within
-the OpenSolaris consolidations.
 
-Common uses:
+The Parent
 
-Show diffs relative to parent workspace			- pdiffs
-Check source style rules				- nits
-Run pre-putback checks					- pbchk
-Collapse all your changes into a single changeset	- recommit'''
+    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.
+
+    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.
 
 
-import atexit, os, re, stat, sys, termios
+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
@@ -63,7 +104,6 @@
 except Version.VersionMismatch, badversion:
     raise util.Abort("Version Mismatch:\n %s\n" % badversion)
 
-import ConfigParser
 from mercurial import cmdutil, ignore, node
 
 from onbld.Scm.WorkSpace import ActiveEntry, WorkSpace
@@ -80,11 +120,12 @@
         prompt = ' [y/N]:'
         defanswer = 'n'
 
-    if Version.at_least("1.3.0"):
-        resp = ui.prompt(msg + prompt, ['&yes', '&no'], default=defanswer)
+    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, r'([Yy(es)?|[Nn]o?)?',
-                         default=defanswer)
+        resp = ui.prompt(msg + prompt, ['&yes', '&no'], default=defanswer)
 
     return resp[0] in ('Y', 'y')
 
@@ -170,12 +211,7 @@
     if repo.local() and repo not in wslist:
         wslist[repo] = WorkSpace(repo)
 
-        if Version.at_least("1.3"):
-            interactive = ui.interactive()
-        else:
-            interactive = ui.interactive
-
-        if interactive and sys.stdin.isatty():
+        if ui.interactive() and sys.stdin.isatty():
             ui.setconfig('hooks', 'preoutgoing.cdm_pbconfirm',
                          'python:hgext_cdm.pbconfirm')
 
@@ -196,10 +232,15 @@
 
 
 def cdm_pdiffs(ui, repo, *pats, **opts):
-    '''list workspace diffs relative to parent workspace
+    '''diff workspace against its parent
+
+    Show differences between this workspace and its parent workspace
+    in the same manner as 'hg diff'.
 
-    The parent tip is taken to be the latest revision shared between
-    us and the parent workspace.'''
+    For a description of the changeset used to represent the parent
+    workspace, see The Parent in the extension documentation ('hg help
+    cdm').
+    '''
 
     parent = opts['parent']
 
@@ -209,10 +250,23 @@
 
 
 def cdm_list(ui, repo, **opts):
-    '''list files changed relative to parent workspace
+    '''list active files (those changed in this workspace)
+
+    Display a list of files changed in this workspace as compared to
+    its parent workspace.
 
-    The parent tip is taken to be the latest revision shared between
-    us and the 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.
+    '''
 
     wanted = []
 
@@ -252,7 +306,8 @@
 
 
 def cdm_bugs(ui, repo, parent=None):
-    'show all bug IDs in checkin comments'
+    '''show all bug IDs referenced in changeset comments'''
+
     act = wslist[repo].active(parent)
 
     for elt in set(filter(Comments.isBug, act.comments())):
@@ -260,7 +315,7 @@
 
 
 def cdm_comments(ui, repo, parent=None):
-    'show checkin comments for active files'
+    '''show changeset commentary for all active changesets'''
     act = wslist[repo].active(parent)
 
     for elt in act.comments():
@@ -270,11 +325,12 @@
 def cdm_renamed(ui, repo, parent=None):
     '''show renamed active files
 
-    Renamed files are shown in the format
+    Renamed files are shown in the format::
 
-       newname oldname
+       new-name old-name
 
-    One pair per-line.'''
+    One pair per-line.
+    '''
 
     act = wslist[repo].active(parent)
 
@@ -283,9 +339,13 @@
 
 
 def cdm_comchk(ui, repo, **opts):
-    '''check checkin comments for active files
+    '''check active changeset comment formatting
+
+    Check that active changeset comments conform to O/N rules.
 
-    Check that checkin 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'))
 
@@ -296,10 +356,18 @@
 
 
 def cdm_cddlchk(ui, repo, *args, **opts):
-    '''check for a valid CDDL block in active files
+    '''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.
 
-    See http://www.opensolaris.org/os/community/on/devref_toc/devref_7/#7_2_3_nonformatting_considerations
-    for more info.'''
+    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')
@@ -326,11 +394,16 @@
 
 
 def cdm_mapfilechk(ui, repo, *args, **opts):
-    '''check for a valid MAPFILE header block in active files
+    '''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).'''
+    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')
@@ -346,7 +419,7 @@
     # We also ignore files with suffixes that tell us that the files
     # are not mapfiles.
     MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
-    	re.IGNORECASE)
+        re.IGNORECASE)
     NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
 
     ui.write('Mapfile comment check:\n')
@@ -367,12 +440,19 @@
 
 
 def cdm_copyright(ui, repo, *args, **opts):
-    '''check active files for valid copyrights
+    '''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.
 
-    Check that all active files have a valid copyright containing the
-    current year (and *only* the current year).
-    See http://www.opensolaris.org/os/project/muskoka/on_dev/golden_rules.txt
-    for more info.'''
+    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')
@@ -394,7 +474,17 @@
 
 
 def cdm_hdrchk(ui, repo, *args, **opts):
-    '''check active header files conform to O/N rules'''
+    '''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')
@@ -420,7 +510,16 @@
 def cdm_cstyle(ui, repo, *args, **opts):
     '''check active C source files conform to the C Style Guide
 
-    See http://opensolaris.org/os/community/documentation/getting_started_docs/cstyle.ms.pdf'''
+    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')
@@ -446,7 +545,11 @@
 
 
 def cdm_jstyle(ui, repo, *args, **opts):
-    'check active Java source files for common stylistic errors'
+    '''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')
@@ -470,7 +573,14 @@
 
 
 def cdm_permchk(ui, repo, *args, **opts):
-    '''check active files permission - warn +x (execute) mode'''
+    '''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')
@@ -499,9 +609,13 @@
 
 
 def cdm_tagchk(ui, repo, **opts):
-    '''check if .hgtags is active and issue warning
+    '''check modification of workspace tags
+
+    Check for any modification of the repository's .hgtags file.
 
-    Tag sharing among repositories is restricted to gatekeepers'''
+    With the exception of the gatekeepers, nobody should introduce or
+    modify a repository's tags.
+    '''
 
     active = wslist[repo].active(opts.get('parent'))
 
@@ -525,8 +639,12 @@
 
 
 def cdm_branchchk(ui, repo, **opts):
-    '''check if multiple heads (or branches) are present, or if
-    branch changes are made'''
+    '''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')
 
@@ -578,7 +696,14 @@
 
 
 def cdm_keywords(ui, repo, *args, **opts):
-    '''check source files do not contain SCCS keywords'''
+    '''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')
@@ -663,8 +788,22 @@
 def cdm_nits(ui, repo, *args, **opts):
     '''check for stylistic nits in active files
 
-    Run copyright, cstyle, hdrchk, jstyle, mapfilechk,
-    permchk, and keywords checks.'''
+    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)
+      - 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,
@@ -678,16 +817,35 @@
 
 
 def cdm_pbchk(ui, repo, **opts):
-    '''pre-putback check all active files
+    '''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):
 
-    Run comchk, copyright, cstyle, hdrchk, jstyle, mapfilechk,
-    permchk, tagchk, branchchk and keywords checks.  Additionally,
-    warn about uncommitted changes.'''
+      - 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)
+      - 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 run
+    # run first in the same order as they would in cdm_nits, then the
+    # pbchk specifics are run.
     #
     cmds = [cdm_copyright,
         cdm_cstyle,
@@ -845,16 +1003,23 @@
 
 
 def cdm_eval(ui, repo, *command, **opts):
-    '''run cmd for each active file
+    '''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:
 
-    cmd can refer to:
-      $file      -	active file basename.
-      $dir       -	active file dirname.
-      $filepath  -	path from workspace root to active file.
-      $workspace -	full path to workspace root.
+      :$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.'''
+    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)
@@ -864,9 +1029,14 @@
 
 
 def cdm_apply(ui, repo, *command, **opts):
-    '''apply cmd to all active files
+    '''apply specified command to all active files
 
-    For example 'hg apply wc -l' outputs a line count of 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'])
 
@@ -881,37 +1051,14 @@
     do_eval(cmd, files, repo.root, not opts['remain'])
 
 
-def cdm_reparent_11(ui, repo, parent):
+def cdm_reparent(ui, repo, parent):
     '''reparent your workspace
 
-    Updates the 'default' path.'''
-
-    filename = repo.join('hgrc')
-
-    p = ui.expandpath(parent)
-    cp = util.configparser()
-
-    try:
-        cp.read(filename)
-    except ConfigParser.ParsingError, inst:
-        raise util.Abort('failed to parse %s\n%s' % (filename, inst))
-
-    try:
-        fh = open(filename, 'w')
-    except IOError, e:
-        raise util.Abort('Failed to open workspace configuration: %s' % e)
-
-    if not cp.has_section('paths'):
-        cp.add_section('paths')
-    cp.set('paths', 'default', p)
-    cp.write(fh)
-    fh.close()
-
-
-def cdm_reparent_13(ui, repo, parent):
-    '''reparent your workspace
-
-    Updates the 'default' path in this repository's .hg/hgrc.'''
+    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
@@ -982,11 +1129,6 @@
 
         update_parent(path, int(target), parent)
 
-if Version.at_least("1.3"):
-    cdm_reparent = cdm_reparent_13
-else:
-    cdm_reparent = cdm_reparent_11
-
 
 def backup_name(fullpath):
     '''Create a backup directory name based on the specified path.
@@ -1018,9 +1160,31 @@
 
 
 def cdm_backup(ui, repo, if_newer=False):
-    '''make backup copies of all workspace changes
+    '''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.
 
-    Backups will be stored in ~/cdm.backup/<basename of 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)
@@ -1041,8 +1205,18 @@
 def cdm_restore(ui, repo, backup, **opts):
     '''restore workspace from backup
 
-    Restores a workspace from the specified backup directory and generation
-    (which defaults to the latest).'''
+    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')
@@ -1069,9 +1243,11 @@
 
 
 def cdm_webrev(ui, repo, **opts):
-    '''generate webrev and optionally upload it
+    '''generate web-based code review and optionally upload it
 
-    This command passes all arguments to webrev script'''
+    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():
@@ -1090,7 +1266,7 @@
 
 cmdtable = {
     'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'),
-                          ('r', 'remain', None, 'do not change directories')],
+                          ('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')],
@@ -1112,7 +1288,7 @@
     'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')],
                'hg cstyle [-p PARENT]'),
     'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'),
-                        ('r', 'remain', None, 'do not change directories')],
+                        ('r', 'remain', None, 'do not change directory')],
              'hg eval [-p PARENT] [-r] command...'),
     'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')],
                'hg hdrchk [-p PARENT]'),
@@ -1121,9 +1297,9 @@
     'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')],
                  'hg keywords [-p PARENT]'),
     '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'),
-                                ('r', 'removed', None, 'show removed files'),
                                 ('a', 'added', None, 'show added files'),
-                                ('m', 'modified', None, 'show modified files')],
+                                ('m', 'modified', None, 'show modified files'),
+                                ('r', 'removed', None, 'show removed files')],
                     'hg list [-amrRu] [-p PARENT]'),
     'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')],
                 'hg mapfilechk [-p PARENT]'),
@@ -1142,7 +1318,7 @@
                              ('b', 'ignore-space-change', None,
                               'ignore changes in the amount of white space'),
                              ('B', 'ignore-blank-lines', None,
-                              'ignore changes whos lines are all blank'),
+                              'ignore changes whose lines are all blank'),
                              ('U', 'unified', 3,
                               'number of lines of context to show'),
                              ('I', 'include', [],
@@ -1151,14 +1327,13 @@
                               'exclude names matching the given patterns')],
                'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'),
     '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'),
-                                      ('f', 'force', None, 'force operation'),
                                       ('m', 'message', '',
                                        'use <text> as commit message'),
                                       ('l', 'logfile', '',
                                        'read commit message from file'),
                                       ('u', 'user', '',
                                        'record user as committer')],
-                       'hg recommit [-f] [-p PARENT]'),
+                       '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'),
@@ -1170,8 +1345,7 @@
                             ('D', 'D', '', 'delete remote webrev'),
                             ('I', 'I', '', 'ITS configuration file'),
                             ('i', 'i', '', 'include file'),
-                            ('l', 'l', '', 'extract file list from putback -n'),
-                            ('N', 'N', None, 'supress comments'),
+                            ('N', 'N', None, 'suppress comments'),
                             ('n', 'n', None, 'do not generate webrev'),
                             ('O', 'O', None, 'OpenSolaris mode'),
                             ('o', 'o', '', 'output directory'),
--- a/usr/src/tools/scripts/hg-active.py	Sat Oct 09 16:16:14 2010 -0500
+++ b/usr/src/tools/scripts/hg-active.py	Fri Oct 08 22:44:45 2010 -0400
@@ -44,8 +44,8 @@
 
 
 import getopt, binascii
-from mercurial import hg, ui, util
-from onbld.Scm.WorkSpace import WorkSpace, HgRepoError
+from mercurial import error, hg, ui, util
+from onbld.Scm.WorkSpace import WorkSpace
 
 
 def usage():
@@ -78,7 +78,7 @@
 
     try:
         repository = hg.repository(ui.ui(), wspath)
-    except HgRepoError, e:
+    except error.RepoError, e:
         sys.stderr.write("failed to open repository: %s\n" % e)
         sys.exit(1)