changeset 10263:56e344f46306

6747190 Cadmium commands do not expand hgrc paths 6791858 Cadmium active list is incorrect if parent workspace contains no changesets 6831741 Many cadmium commands ignore -p/--parent
author Richard Lowe <richlowe@richlowe.net>
date Wed, 05 Aug 2009 16:12:50 -0700
parents f0a682e100e9
children 1196af6129ec
files usr/src/tools/onbld/Scm/WorkSpace.py usr/src/tools/onbld/hgext/cdm.py
diffstat 2 files changed, 112 insertions(+), 166 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/tools/onbld/Scm/WorkSpace.py	Wed Aug 05 15:46:35 2009 -0700
+++ b/usr/src/tools/onbld/Scm/WorkSpace.py	Wed Aug 05 16:12:50 2009 -0700
@@ -440,70 +440,64 @@
         self.ui = self.repo.ui
         self.name = self.repo.root
 
-        parent = self.repo.ui.expandpath('default')
-        if parent == 'default':
-            parent = None
-        self.parentrepo = parent
-
         self.activecache = {}
         self.outgoingcache = {}
 
     def parent(self, spec=None):
-        '''Return canonical workspace parent, either SPEC if passed,
-        or default parent otherwise'''
-        return spec or self.parentrepo
+        '''Return the canonical workspace parent, either SPEC (which
+        will be expanded) if provided or the default parent
+        otherwise.'''
 
-    def _localtip(self, bases, heads):
-        '''Return a tuple (changectx, workingctx) representing the most
-        representative head to act as the local tip.
+        if spec:
+            return self.ui.expandpath(spec)
 
-        If the working directory is modified, the changectx is its
-        tipmost local parent (or tipmost parent, if neither is
-        local), and the workingctx is non-null.
+        p = self.ui.expandpath('default')
+        if p == 'default':
+            return None
+        else:
+            return p
 
-        If the working directory is clean, the workingctx is null.
-        The changectx is the tip-most local head on the current branch.
-        If this can't be determined for some reason (e.g., the parent
-        repo is inacessible), changectx is the tip-most head on the
-        current branch.
+    def _localtip(self, outgoing, wctx):
+        '''Return the most representative changeset to act as the
+        localtip.
 
-        If the workingctx is non-null it is the actual local tip (and would
-        be the local tip in any generated ActiveList, for instance),
-        the better parent revision is returned also to aid callers needing
-        a real changeset to act as a surrogate for an uncommitted change.'''
+        If the working directory is modified (has file changes, is a
+        merge, or has switched branches), this will be a workingctx.
 
-        def tipmost_of(nodes):
-            return sorted(nodes, cmp=lambda x, y: cmp(x.rev(), y.rev()))[-1]
+        If the working directory is unmodified, this will be the most
+        recent (highest revision number) local (outgoing) head on the
+        current branch, if no heads are determined to be outgoing, it
+        will be the most recent head on the current branch.
+        '''
 
         #
-        # We need a full set of outgoing nodes such that we can limit
-        # local branch heads to those which are outgoing
-        #
-        outnodes = self.repo.changelog.nodesbetween(bases, heads)[0]
-        wctx = self.workingctx()
-
-        #
-        # A modified working context is seen as a proto-branch, where
-        # the 'heads' from our view are the parent revisions of that
-        # context.
-        # (and the working head is it)
+        # A modified working copy is seen as a proto-branch, and thus
+        # our only option as the local tip.
         #
         if (wctx.files() or len(wctx.parents()) > 1 or
             wctx.branch() != wctx.parents()[0].branch()):
-            heads = wctx.parents()
-        else:
-            heads = [self.repo.changectx(n) for n in heads]
-            wctx = None
+            return wctx
+
+        heads = self.repo.heads(start=wctx.parents()[0].node())
+        headctxs = [self.repo.changectx(n) for n in heads]
+        localctxs = [c for c in headctxs if c.node() in outgoing]
+
+        ltip = sorted(localctxs or headctxs, key=lambda x: x.rev())[-1]
 
-        localchoices = [n for n in heads if n.node() in outnodes]
-        return (tipmost_of(localchoices or heads), wctx)
+        if len(heads) > 1:
+            self.ui.warn('The current branch has more than one head, '
+                         'using %s\n' % ltip.rev())
+
+        return ltip
 
-    def _parenttip(self, localtip, parent=None):
-        '''Find the closest approximation of the parents tip, as best
-        as we can.
+    def _parenttip(self, heads, outgoing):
+        '''Return the highest-numbered, non-outgoing changeset that is
+        an ancestor of a changeset in heads.
 
-        In parent-less workspaces returns our tip (given the best
-        we can do is deal with uncommitted changes)'''
+        This is intended to find the most recent changeset on a given
+        branch that is shared between a parent and child workspace,
+        such that it can act as a stand-in for the parent workspace.
+        '''
 
         def tipmost_shared(head, outnodes):
             '''Return the tipmost node on the same branch as head that is not
@@ -514,15 +508,20 @@
             and return the first node we see in the iter phase that
             was previously collected.
 
+            If no node 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.'''
 
+            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, {'rev': ['%s:0' % head],
-                                                      'follow': True})[0]
+                                                get, opts)[0]
             seen = []
             for st, rev, fns in changeiter:
                 n = self.repo.changelog.node(rev)
@@ -532,22 +531,10 @@
                 elif st == 'iter':
                     if n in seen:
                         return rev
-            return None
-
-        tipctx, wctx = localtip
-        parent = self.parent(parent)
-        outgoing = None
+            return self.repo.changelog.rev(node.nullid)
 
-        if parent:
-            outgoing = self.findoutgoing(parent)
-
-        if wctx:
-            possible_branches = wctx.parents()
-        else:
-            possible_branches = [tipctx]
-
-        nodes = self.repo.changelog.nodesbetween(outgoing)[0]
-        ptips = map(lambda x: tipmost_shared(x.rev(), nodes), possible_branches)
+        nodes = set(outgoing)
+        ptips = map(lambda x: tipmost_shared(x, nodes), heads)
         return self.repo.changectx(sorted(ptips)[-1])
 
     def status(self, base=None, head=None):
@@ -614,30 +601,26 @@
 
         if parent:
             outgoing = self.findoutgoing(parent)
+            outnodes = self.repo.changelog.nodesbetween(outgoing)[0]
         else:
             outgoing = []       # No parent, no outgoing nodes
+            outnodes = []
 
-        branchheads = self.repo.heads(start=self.repo.dirstate.parents()[0])
-        ourhead, workinghead = self._localtip(outgoing, branchheads)
+        localtip = self._localtip(outnodes, self.workingctx())
 
-        if len(branchheads) > 1:
-            self.ui.warn('The current branch has more than one head, '
-                         'using %s\n' % ourhead.rev())
+        if localtip.rev() is None:
+            heads = localtip.parents()
+        else:
+            heads = [localtip]
 
-        if workinghead:
-            parents = workinghead.parents()
-            ctxs = [self.repo.changectx(n) for n in
-                    self.repo.changelog.nodesbetween(outgoing,
-                                                     [h.node() for h in
-                                                      parents])[0]]
-            ctxs.append(workinghead)
-        else:
-            ctxs = [self.repo.changectx(n) for n in
-                    self.repo.changelog.nodesbetween(outgoing,
-                                                     [ourhead.node()])[0]]
+        ctxs = [self.repo.changectx(n) for n in
+                self.repo.changelog.nodesbetween(outgoing,
+                                                 [h.node() for h in heads])[0]]
 
-        act = ActiveList(self, self._parenttip((ourhead, workinghead), parent),
-                         ctxs)
+        if localtip.rev() is None:
+            ctxs.append(localtip)
+
+        act = ActiveList(self, self._parenttip(heads, outnodes), ctxs)
 
         self.activecache[parent] = act
         return act
--- a/usr/src/tools/onbld/hgext/cdm.py	Wed Aug 05 15:46:35 2009 -0700
+++ b/usr/src/tools/onbld/hgext/cdm.py	Wed Aug 05 16:12:50 2009 -0700
@@ -75,47 +75,22 @@
         return False
 
 
-def _buildfilelist(repo, args):
-    '''build a list of files in which we're interested
-
-    If no files are specified, then we'll default to using
-    the entire active list.
+def buildfilelist(ws, parent, files):
+    '''Build a list of files in which we're interested.
 
-    Returns a dictionary, wherein the keys are cwd-relative file paths,
-    and the values (when present) are entries from the active list.
-    Instead of warning the user explicitly about files not in the active
-    list, we'll attempt to do checks on them.'''
-
-    fdict = {}
+    If no files are specified take files from the active list relative
+    to 'parent'.
 
-    #
-    # If the user specified files on the command line, we'll only check
-    # those files.  We won't pull the active list at all.  That means we
-    # won't be smart about skipping deleted files and such, so the user
-    # needs to be smart enough to not explicitly specify a nonexistent
-    # file path.  Which seems reasonable.
-    #
-    if args:
-        for f in args:
-            fdict[f] = None
+    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.'''
 
-    #
-    # Otherwise, if no files were listed explicitly, we assume that the
-    # checks should be run on all files in the active list.  So we determine
-    # it here.
-    #
-    # Tracking the file paths is a slight optimization, in that multiple
-    # check functions won't need to derive it themselves.  This also dovetails
-    # nicely with the expectation that explicitly specified files will be
-    # ${CWD}-relative paths, so the fdict keyspace will be consistent either
-    # way.
-    #
+    if files:
+        return [(path, None) for path in sorted(files)]
     else:
-        active = wslist[repo].active()
-        for e in sorted(active):
-            fdict[wslist[repo].filepath(e.name)] = e
-
-    return fdict
+        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):
@@ -303,16 +278,14 @@
     See http://www.opensolaris.org/os/community/on/devref_toc/devref_7/#7_2_3_nonformatting_considerations
     for more info.'''
 
-    filelist = opts.get('filelist') or _buildfilelist(repo, args)
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'cddlchk')
+    lenient = True
+    ret = 0
 
     ui.write('CDDL block check:\n')
 
-    lenient = True
-    ret = 0
-
-    exclude = not_check(repo, 'cddlchk')
-
-    for f, e in filelist.iteritems():
+    for f, e in filelist:
         if e and e.is_removed():
             continue
         elif (e or opts.get('honour_nots')) and exclude(f):
@@ -336,14 +309,13 @@
     header comment directing the reader to the document containing
     Solaris object versioning rules (README.mapfile).'''
 
-    filelist = opts.get('filelist') or _buildfilelist(repo, args)
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'mapfilechk')
+    ret = 0
 
     ui.write('Mapfile comment check:\n')
 
-    ret = 0
-    exclude = not_check(repo, 'mapfilechk')
-
-    for f, e in filelist.iteritems():
+    for f, e in filelist:
         if e and e.is_removed():
             continue
         elif f.find('mapfile') == -1:
@@ -366,14 +338,13 @@
     See http://www.opensolaris.org/os/project/muskoka/on_dev/golden_rules.txt
     for more info.'''
 
-    filelist = opts.get('filelist') or _buildfilelist(repo, args)
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'copyright')
+    ret = 0
 
     ui.write('Copyright check:\n')
 
-    ret = 0
-    exclude = not_check(repo, 'copyright')
-
-    for f, e in filelist.iteritems():
+    for f, e in filelist:
         if e and e.is_removed():
             continue
         elif (e or opts.get('honour_nots')) and exclude(f):
@@ -389,14 +360,13 @@
 def cdm_hdrchk(ui, repo, *args, **opts):
     '''check active header files conform to O/N rules'''
 
-    filelist = opts.get('filelist') or _buildfilelist(repo, args)
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'hdrchk')
+    ret = 0
 
     ui.write('Header format check:\n')
 
-    ret = 0
-    exclude = not_check(repo, 'hdrchk')
-
-    for f, e in filelist.iteritems():
+    for f, e in filelist:
         if e and e.is_removed():
             continue
         elif not f.endswith('.h'):
@@ -416,14 +386,13 @@
 
     See http://opensolaris.org/os/community/documentation/getting_started_docs/cstyle.ms.pdf'''
 
-    filelist = opts.get('filelist') or _buildfilelist(repo, args)
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'cstyle')
+    ret = 0
 
     ui.write('C style check:\n')
 
-    ret = 0
-    exclude = not_check(repo, 'cstyle')
-
-    for f, e in filelist.iteritems():
+    for f, e in filelist:
         if e and e.is_removed():
             continue
         elif not (f.endswith('.c') or f.endswith('.h')):
@@ -443,14 +412,13 @@
 def cdm_jstyle(ui, repo, *args, **opts):
     'check active Java source files for common stylistic errors'
 
-    filelist = opts.get('filelist') or _buildfilelist(repo, args)
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'jstyle')
+    ret = 0
 
     ui.write('Java style check:\n')
 
-    ret = 0
-    exclude = not_check(repo, 'jstyle')
-
-    for f, e in filelist.iteritems():
+    for f, e in filelist:
         if e and e.is_removed():
             continue
         elif not f.endswith('.java'):
@@ -468,14 +436,13 @@
 def cdm_permchk(ui, repo, *args, **opts):
     '''check active files permission - warn +x (execute) mode'''
 
-    filelist = opts.get('filelist') or _buildfilelist(repo, args)
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'permchk')
+    exeFiles = []
 
     ui.write('File permission check:\n')
 
-    exeFiles = []
-    exclude = not_check(repo, 'permchk')
-
-    for f, e in filelist.iteritems():
+    for f, e in filelist:
         if e and e.is_removed():
             continue
         elif (e or opts.get('honour_nots')) and exclude(f):
@@ -606,14 +573,13 @@
 def cdm_keywords(ui, repo, *args, **opts):
     '''check source files do not contain SCCS keywords'''
 
-    filelist = opts.get('filelist') or _buildfilelist(repo, args)
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'keywords')
+    ret = 0
 
     ui.write('Keywords check:\n')
 
-    ret = 0
-    exclude = not_check(repo, 'keywords')
-
-    for f, e in filelist.iteritems():
+    for f, e in filelist:
         if e and e.is_removed():
             continue
         elif (e or opts.get('honour_nots')) and exclude(f):
@@ -671,20 +637,17 @@
 
     ret = 0
 
-    flist = _buildfilelist(ws.repo, args)
-
     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()
 
-            result = cmd(ws.ui, ws.repo, filelist=flist,
-                         honour_nots=True, *args, **opts)
             ret |= result
 
-            output = ws.ui.popbuffer()
             if not ws.ui.quiet or result != 0:
                 ws.ui.write(output, '\n')
     return ret