Graham Helliwell avatar Graham Helliwell committed 281a7d9 Merge

Merge with PullUpdate and SiblingGuests

Comments (0)

Files changed (3)

 The .hgguestrepo file defines guests by specifying three things:
 
  * location: The location of the guest relative to the root of the parent
-   repository.
+   repository. A path outside the root is allowed if a .grauth file exists
+   in any parent directory of the intended location.
  * remote id: The name of a remote path defined in the .hggrmapping file.
  * changeset id: A Mercurial identifier e.g., a tag, bookmark, changeset
    hash, branch, or other future identifier resolvable to a
 
 ###Sync
 
-`hg grsync [guestrepo...]`
+`hg grsync[guestrepo...]`
 
 Update each guest repo to the changeset identifier in the .hgguestrepo file.
 The changeset identifier can be a branch, tag, changeset hex, bookmark, or
 
 Contributors:
 Ben Smith
+Graham Helliwell

guestrepo/__init__.py

     mercurial repository.
 '''
 
-from guestrepo import pull, push, grupdate, freeze, summary, state, grout, grin
+from guestrepo import grpull, grpush, grupdate, freeze, summary, state, grout, grin
 
 import convert
 import lockedui
 localopt = [('l', 'local', False,
            "use the .hggrmapping file in the working directory")]
 
+pullopt = [('p', 'pull', False,
+           "perform a grpull before the grupdate")]
+
+updateopt = [('u', 'update', False,
+           "perform a grupdate after the grpull")]
+
+forcepartialopt = [('f', 'forcepartial', False,
+           "force a partial update by silently ignoring disallowed paths")]
+
 threadopt = [('j', 'threads', 1,
           'number of threads to use. '
           'WARNING: ssh prompts are broken, and username and password prompts '
 # See the readme for a description of each command
 # format: {"command-name": (function, options-list, help-string)}
 cmdtable = {
-   "grpull": (pull,
-              localopt + threadopt,
+   "grpull": (grpull,
+              localopt + threadopt + updateopt + forcepartialopt,
               "hg grpull [REPO [+]] [options]"),
-   "grpush": (push,
-              localopt + threadopt,
+   "grpush": (grpush,
+              localopt + threadopt + forcepartialopt,
               "hg grpush [REPO [+]] [options]"),
    "grupdate": (grupdate,
-              localopt + threadopt,
+              localopt + threadopt + pullopt + forcepartialopt,
               "hg grupdate [REPO [+]] [options]"),
    "grfreeze": (freeze,
               [],

guestrepo/guestrepo.py

 
 GR_CONFIG = '.hgguestrepo'
 GR_MAPPING = '.hggrmapping'
+GR_AUTHFILENAME = '.grauth'
 
 #####################
 # Commands
     the current branch. Use this option if hggrmapping is modified but not yet
     committed.
 
+    The `--pull` flag causes a grpull to happen before the grupdate. If the grpull
+    fails the grupdate will not begin.
+
     see help for the guestrepo extension for more information on the files used
     '''
-    local = opts.get('local')
+    actions = [pull] if opts.get('pull') else []
+    actions.append(update)
+    return repoactions(ui, repo, actions, args, opts)
 
-    guests = getguests(repo)
-    if args:
-        guests = matchguests(repo.root,
-                             os.getcwd(),
-                             args,
-                             guests)
-
-    path = dirtyrecursive(ui, repo, local, guests)
-    if path:
-        raise util.Abort("repository %s contains uncommitted changes" % path)
-
-    def syncaction(ui, repo, guest):
-        ui.status('GR updating %s\n' % guest.canonpath)
-        warnbranch(ui, guest.name, repo, guest.csid)
-        commands.update(ui, repo, guest.csid)
-
-    mapping = True
-    if local:
-        mapping = 'local'
-
-    workers = makeworkers(ui, opts)
-    try:
-        applyguests(ui, repo, guests, workers, syncaction,
-                    [], mapping, True)
-    finally:
-        workers.join()
-    if len(workers.errors) > 0:
-        showerrors(ui, workers)
-        return 1
-    else:
-        return 0
-
-def push(ui, repo, *args, **opts):
+def grpush(ui, repo, *args, **opts):
     '''push guest repos
 
     Push each guest to the path specified in the mapping file.
 
     see help for the guestrepo extension for more information on the files used
     '''
+    return repoactions(ui, repo, [push], args, opts)
 
-    guests = getguests(repo)
-    if args:
-        guests = matchguests(repo.root,
-                             os.getcwd(),
-                             args,
-                             guests)
-
-    def pushaction(ui, repo, guest):
-        commands.push(ui, repo, guest.uri)
-
-    mapping = True
-    if opts.get('local'):
-        mapping = 'local'
-
-    workers = makeworkers(ui, opts)
-    try:
-        applyguests(ui, repo, guests, workers, pushaction,
-                    [], mapping, False)
-    finally:
-        workers.join()
-    if len(workers.errors) > 0:
-        showerrors(ui, workers)
-        return 1
-    else:
-        return 0
-
-def pull(ui, repo, *args, **opts):
+def grpull(ui, repo, *args, **opts):
     '''pull  guest repos
 
     Pull each guest repo from the location specified in the mapping file.
     the current branch. Use this option if hggrmapping is modified but not yet
     committed.
 
+    The `--update` flag causes a grupdate to happen after the grpull. If the grpull
+    fails the grupdate will not begin. Equivalent to grupdate --pull.
+
     see help for the guestrepo extension for more information on the files used
     '''
-
-    guests = getguests(repo)
-    if args:
-        guests = matchguests(repo.root,
-                             os.getcwd(),
-                             args,
-                             guests)
-
-    def pullaction(ui, repo, guest):
-        ui.status('pulling %s\n' % guest.canonpath)
-        commands.pull(ui, repo, guest.uri)
-
-    mapping = True
-    if opts.get('local'):
-        mapping = 'local'
-
-    workers = makeworkers(ui, opts)
-    try:
-        applyguests(ui, repo, guests, workers, pullaction,
-                    [], mapping, True)
-    finally:
-        workers.join()
-    if len(workers.errors) > 0:
-        showerrors(ui, workers)
-        return 1
-    else:
-        return 0
+    actions = [pull]
+    if opts.get('update'): actions.append(update)
+    return repoactions(ui,repo, actions, args, opts)
 
 def summary(ui, repo, **opts):
     '''print a summary of the guest repos
    def action(ui, repo, guest):
       commands.outgoing(ui, repo, guest.uri)
 
-   workers = makeworkers(ui, opts)
-
-   try:
-      applyguests(ui, repo, guests, workers, action,
-                    [], mapping, False)
-   finally:
-      workers.join()
-   if len(workers.errors) > 0:
-      showerrors(ui, workers)
-      return 1
-   else:
-      return 0
-
+   return 0 if runparallelaction(ui, repo, mapping, guests, action, False, opts) else 1
 
 def grin(ui,repo,**opts):
    """
    def action(ui, repo, guest):
       commands.incoming(ui, repo, guest.uri, **opts)
 
-   workers = makeworkers(ui, opts)
+   return 0 if runparallelaction(ui, repo, mapping, guests, action, False, opts) else 1
 
-   try:
-      applyguests(ui, repo, guests, workers, action,
-                    [], mapping, False)
-   finally:
-      workers.join()
-   if len(workers.errors) > 0:
-      showerrors(ui, workers)
-      return 1
-   else:
-      return 0
+#####################
 
+def repoactions(ui, repo, orderedactions, args, opts):
+    '''Assumes orderedactions is non-empty. Returns true iff all actions fail.'''
+    local = opts.get('local')
+    mapping = 'local' if local else True
+    continueOnError = opts.get('forcepartial')
 
+    try:
+        guests = getguests(repo, None, continueOnError)
+    except IOError:
+        ui.warn("No guest repositories are configured for repository at '{0}'.\n".format(repo.origroot))
+        return 0
+
+    if args:
+       guests = matchguests(repo.root, os.getcwd(), args, guests)
+
+    failures = list(not action(ui, repo, mapping, guests, opts) for action in orderedactions )
+    return any(failures)
+
+def pull(ui, repo, mapping, guests, opts):
+    def pullaction(ui, repo, guest):
+        ui.status('pulling %s\n' % guest.canonpath)
+        commands.pull(ui, repo, guest.uri)
+
+    runparallelaction(ui, repo, mapping, guests, pullaction, True, opts)
+
+def update(ui, repo, mapping, guests, opts):
+    local = mapping=='local'
+    path = dirtyrecursive(ui, repo, local, guests)
+    if path:
+        raise util.Abort("repository %s contains uncommitted changes" % path)
+
+    def updateaction(ui, repo, guest):
+        ui.status('GR updating %s\n' % guest.canonpath)
+        warnbranch(ui, guest.name, repo, guest.csid)
+        commands.update(ui, repo, guest.csid)
+
+    runparallelaction(ui, repo, mapping, guests, updateaction, True, opts)
+
+def push(ui, repo, mapping, guests, opts):
+    def pushaction(ui, repo, guest):
+        commands.push(ui, repo, guest.uri)
+    runparallelaction(ui, repo, mapping, guests, pushaction, False, opts)
+
+def runparallelaction(ui, repo, mapping, guests, action, create, opts):
+    workers = makeworkers(ui, opts)
+    try:
+        applyguests(ui, repo, guests, workers, action,
+                    [], mapping, create)
+    finally:
+        workers.join()
+    if len(workers.errors) > 0:
+        showerrors(ui, workers)
+        return False
+    else:
+        return True
 #####################
 
 class guestrepo(object):
     return mappingconfig['']
 
 
-def getguests(repo, ctx=None):
+def getguests(repo, ctx=None, skipUnauthorized=False):
     ''' Get the guest repos by parsing the .hgguestrepo.
 
         The uri field of the guests is not set until they are matched using the
     guests = []
     pathauditor = scmutil.pathauditor(repo.root)
     for configpath, value in guestconfig[''].items():
-        canonpath = scmutil.canonpath(repo.root,
+        try:
+           canonpath = scmutil.canonpath(repo.root,
                                       repo.root,
                                       configpath,
                                       auditor = pathauditor)
+        except util.Abort:
+               canonpath = authorizedoutisderoot(os.path.join(repo.root, configpath), skipUnauthorized)
+
+        if canonpath == None:
+           continue
+
         if canonpath == '':
             raise util.Abort("guest path '%s' refers to parent repository!" %
                                 configpath)
 
     return guests
 
+def authorizedoutisderoot(path, continueOnError=False):
+    '''A path is authorized if anywhere above it in the file tree there's a file called .grauth
+       If the given path is authorized the normalized path is returned.
+       If the given path is not authorized, an Abort exception is thrown if abortIfInvalid==True, or None is returned otherwise.
+    '''
+    #Don't check the path itself
+    path = util.pconvert(os.path.normpath(path))
+    previouspath = path
+
+    while True:
+          currentpath, p = os.path.split(previouspath)
+          if currentpath == previouspath:
+             break
+
+          authfile = os.path.join(currentpath, GR_AUTHFILENAME)
+          if os.path.exists(authfile):
+             return path
+          previouspath = currentpath
+
+    bestgrauthlocation, p = os.path.split(path)
+    bestgrauthlocation = os.path.join(bestgrauthlocation, GR_AUTHFILENAME)
+    if not continueOnError:
+       raise util.Abort("'{0}' not under root. To override this check, add an empty file called '{1}'".format(path, bestgrauthlocation))
+    return None
+
 def rejectnestedguests(guests):
     ''' Given a collection of guests, throw an exception if the path of any
         guest is a prefix of the path of any other guest
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.