Merge. Tested to run on 2.9 (no errors) and 3.6+ aka tip. 3.6 had some
cosmetic errors, but no substantiative errors visible in the tests.
3 files changed, 77 insertions(+), 77 deletions(-)

M README.md
M guestrepo/__init__.py
M guestrepo/guestrepo.py
M README.md +8 -0
@@ 255,6 255,14 @@ Update will present an error and do noth
 guest repository.
 This prevents the update from causing a merge with local changes.
 
+The --clean option allows you to "disable" (by running the "hg up null"
+command) any non-guest repository found under the shell directory. This allows
+the user to ensure the shel repository is really in the state described by the
+.hgguestrepo file (on can also purge each of theses non-guest repositories by
+adding the --purge option). This is usefull for example to run tests (in a
+controller environment) in a setup where there are several branches in the
+shell repository, with different set of guest repositories referenced among the
+branches.
 
 ###Freeze
 

          
M guestrepo/__init__.py +2 -1
@@ 133,7 133,8 @@ cmdtable = {
    "grupdate": (grupdate,
                 localopt + threadopt
                 # TODO: clean may be the wrong term.
-                + [('C', 'clean', False, 'ensure every non-guest subrepo is updated to revision -1')]
+                + [('C', 'clean', False, 'ensure every non-guest repos is updated to revision -1')]
+                + [('P', 'purge', False, 'also run a "hg purge" in guest repos when cleaning (--clean)')]
                 + [('p', 'pull', False, "grpull before running grupdate")]
                 + [('f', 'file', '', "update using a .hgguestrepo file", 'FILE')],
                 "hg grupdate [REPO [+]] [options]"),

          
M guestrepo/guestrepo.py +67 -76
@@ 56,6 56,8 @@ try:
     from mercurial import pathutil
 except ImportError:
     from mercurial import scmutil as pathutil
+from mercurial import extensions
+
 SUPPORTS_PHASES = None
 try:
    from mercurial.phases import draft

          
@@ 85,6 87,18 @@ def warn_uncomitted_config(func):
         return func(ui, repo, *args, **kwargs)
     return wrapper
 
+ADDED_INDEX = 1
+UNKNOWN_INDEX = 4
+
+def findrepos(root, excludes=[]):
+    excludes = excludes[:]
+    excludes.append(root)
+    for loc, dirs, files in os.walk(root):
+        if '.hg' in dirs:
+            if loc not in excludes:
+                yield loc
+            dirs.remove('.hg')
+
 #####################
 # Commands
 #####################

          
@@ 117,6 131,7 @@ def grupdate(ui, repo, *args, **opts):
 
     local = opts.get('local')
     clean = opts.get('clean')
+    purge = opts.get('purge') and extensions.find('purge')
     if opts.get('file'):
         guests = getguests(ui, repo, opts.get('file'), local=opts.get('local'))
     else:

          
@@ 556,14 571,58 @@ def grin(ui, repo, **opts):
 
 
 #####################
-def findrepos(root, excludes=[]):
-    excludes = excludes[:]
-    excludes.append(root)
-    for loc, dirs, files in os.walk(root):
-        if '.hg' in dirs:
-            if loc not in excludes:
-                yield loc
-            dirs.remove('.hg')
+
+class guestrepo(object):
+    '''An aggregate representing a guest repository'''
+    def __init__(self, name, configpath, canonpath, uri, csid, root):
+        self.name = name
+        self.configpath = configpath
+        self.canonpath = canonpath
+        self.uri = uri
+        self.csid = csid
+        self.root = root
+
+    def isrepo(self, warn=False):
+        return os.path.exists(os.path.join(self.root, '.hg'))
+
+    def to_dict(self, ui):
+        '''
+        Output all of the metadata associated with a Guestrepo as a dict
+        '''
+        guestrepo = hg.repository(ui, self.root, create=False)
+        guestctx = guestrepo[None]
+
+        # Set up GR attributes in JSON
+        json_dict = {'path' : self.canonpath}
+        json_dict['branch'] = guestctx.branch()
+        json_dict['remote_name'] = self.name
+
+        parents = guestctx.parents()
+        json_dict['id'] = (' '.join(['+'.join([node.hex(p.node()) for p in parents]), ]))[:12]
+
+        if guestctx.tags():
+            json_dict['tags'] = '/'.join(guestctx.tags())
+
+        if guestctx.bookmarks():
+            json_dict['bookmarks'] = '/'.join(guestctx.bookmarks())
+
+        current_bookmark = bookmarks.readcurrent(guestrepo)
+
+        if current_bookmark:
+            json_dict['current_bookmark'] = current_bookmark
+
+        if changed(guestrepo):
+            json_dict['changed'] = True
+
+        return json_dict
+
+    def to_gr_entry(self, ui):
+        guestrepo = hg.repository(ui, self.root, create=False)
+        changeset_id = node.hex(guestrepo[None].parents()[0].node())
+        return "%s = %s %s\n" % self.configpath.ljust(10), self.name.ljust(10), changeset_id
+
+    def to_mapping_entry(self, ui):
+        return "%s = %s" % self.configpath.ljust(10), self.name
 
 
 def showerrors(ui, workers):

          
@@ 847,16 906,6 @@ def applyguests(ui, repo, guests, worker
         if work:
             workers.run(work, guest)
 
-def readconfig(filename, ctx):
-    ''' read a config file at a context
-
-        throws IOError when config file missing from the working directory,
-        error. LookupError if config is missing from a changeset
-    '''
-    p = config.config()
-    p.parse(filename, ctx[filename].data())
-    return p
-
 def makestatestr(states):
     '''
     Create a description string for a list of state.

          
@@ 941,61 990,3 @@ def template_output_to_list(template_out
         in_records.append(loaded_object)
 
     return in_records
-
-
-
-# N.b., might be dead code.
-class guestrepo(object):
-    '''An aggregate representing a guest repository'''
-    def __init__(self, name, configpath, canonpath, uri, csid, root):
-        self.name = name
-        self.configpath = configpath
-        self.canonpath = canonpath
-        self.uri = uri
-        self.csid = csid
-        self.root = root
-
-    def isrepo(self, warn=False):
-        return os.path.exists(os.path.join(self.root, '.hg'))
-
-    def to_dict(self, ui):
-        '''
-        Output all of the metadata associated with a Guestrepo as a dict
-        '''
-        guestrepo = hg.repository(ui, self.root, create=False)
-        guestctx = guestrepo[None]
-
-        # Set up GR attributes in JSON
-        json_dict = {'path' : self.canonpath}
-        json_dict['branch'] = guestctx.branch()
-        json_dict['remote_name'] = self.name
-
-        parents = guestctx.parents()
-        json_dict['id'] = (' '.join(['+'.join([node.hex(p.node()) for p in parents]), ]))[:12]
-
-        if guestctx.tags():
-            json_dict['tags'] = '/'.join(guestctx.tags())
-
-        if guestctx.bookmarks():
-            json_dict['bookmarks'] = '/'.join(guestctx.bookmarks())
-
-        try:
-            current_bookmark = bookmarks.readcurrent(guestrepo)
-        except AttributeError:
-            currentbookmark = bookmarks.readactive(guestrepo)
-
-        if current_bookmark:
-            json_dict['current_bookmark'] = current_bookmark
-
-        if changed(guestrepo):
-            json_dict['changed'] = True
-
-        return json_dict
-
-    def to_gr_entry(self, ui):
-        guestrepo = hg.repository(ui, self.root, create=False)
-        changeset_id = node.hex(guestrepo[None].parents()[0].node())
-        return "%s = %s %s\n" % self.configpath.ljust(10), self.name.ljust(10), changeset_id
-
-    def to_mapping_entry(self, ui):
-        return "%s = %s" % self.configpath.ljust(10), self.name