[42223] trunk/dports/devel
blb at macports.org
blb at macports.org
Sun Nov 16 16:52:45 PST 2008
Revision: 42223
http://trac.macports.org/changeset/42223
Author: blb at macports.org
Date: 2008-11-16 16:52:44 -0800 (Sun, 16 Nov 2008)
Log Message:
-----------
New port - devel/hg-forest, mercurial extension for nested repositories
Added Paths:
-----------
trunk/dports/devel/hg-forest/
trunk/dports/devel/hg-forest/Portfile
trunk/dports/devel/hg-forest/files/
trunk/dports/devel/hg-forest/files/forest.py
Added: trunk/dports/devel/hg-forest/Portfile
===================================================================
--- trunk/dports/devel/hg-forest/Portfile (rev 0)
+++ trunk/dports/devel/hg-forest/Portfile 2008-11-17 00:52:44 UTC (rev 42223)
@@ -0,0 +1,39 @@
+# $Id$
+
+PortSystem 1.0
+PortGroup python25 1.0
+name hg-forest
+version 20081025
+categories devel mercurial
+maintainers blb openmaintainer
+description Mercurial extension for nested repositories
+long_description \
+ Operations on trees with nested Mercurial repositories. \
+ This extension provides commands that apply to a composite tree called \
+ a forest. Some commands simply wrap standard Mercurial commands, such \
+ as 'clone' or 'status', and others involve a snapshot file.
+
+platforms darwin
+
+homepage http://www.selenic.com/mercurial/wiki/index.cgi/ForestExtension
+# This is only here since master_sites is a required key
+master_sites macports
+
+depends_lib port:mercurial
+
+fetch {}
+checksum {}
+extract {}
+use_configure no
+build {}
+destroot {
+ xinstall -m 755 -d ${destroot}${python.pkgd}/hgext
+ xinstall -m 644 -W ${filespath} forest.py ${destroot}${python.pkgd}/hgext
+}
+post-activate {
+ ui_msg "To setup forest to run, edit your .hgrc to add:"
+ ui_msg " \[extensions\]"
+ ui_msg " hgext.forest="
+ ui_msg "See ${homepage} for more explanation"
+}
+
Property changes on: trunk/dports/devel/hg-forest/Portfile
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added: trunk/dports/devel/hg-forest/files/forest.py
===================================================================
--- trunk/dports/devel/hg-forest/files/forest.py (rev 0)
+++ trunk/dports/devel/hg-forest/files/forest.py 2008-11-17 00:52:44 UTC (rev 42223)
@@ -0,0 +1,1400 @@
+# Forest, an extension to work on a set of nested Mercurial trees.
+#
+# Copyright (C) 2006 by Robin Farine <robin.farine at terminus.org>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+# Repository path representation
+#
+# Repository paths stored in the filesystem representation are stored
+# in variables named 'rpath'. Repository roots in the mercurial
+# representation, stored in variables named 'root', are used in
+# snapshot files and in command output.
+
+"""Operations on trees with nested Mercurial repositories.
+
+This extension provides commands that apply to a composite tree called
+a forest. Some commands simply wrap standard Mercurial commands, such
+as 'clone' or 'status', and others involve a snapshot file.
+
+A snapshot file represents the state of a forest at a given time. It
+has the format of a ConfigParser file and lists the trees in a forest,
+each tree with the following attributes:
+
+ root path relative to the top-level tree
+ revision the revision the working directory is based on
+ paths a list of (alias, location) pairs
+
+The 'fsnap' command generates or updates such a file based on a forest
+in the file system. Other commands use this information to populate a
+forest or to pull/push changes.
+
+
+Configuration
+
+This extension recognizes the following item in the forest
+configuration section:
+
+walkhg = (0|no|false|1|yes|true)
+
+ Whether repositories directly under a .hg directory should be
+ skipped (0|no|false) or not (1|yes|true). The default value is true.
+ Some commands accept the --walkhg command-line option to override
+ the behavior selected by this item.
+
+partial = (0|no|false|1|yes|true)
+
+ Whether fpull should default to partial. The default value is 0.
+
+"""
+
+import ConfigParser
+import errno
+import os
+import re
+import shutil
+
+from mercurial import cmdutil, commands, hg, hgweb, node, util
+from mercurial import localrepo, sshrepo, sshserver, httprepo, statichttprepo
+from mercurial.i18n import gettext as _
+from mercurial.repo import RepoError
+
+# For backwards compatibility, we need the following function definition.
+# If we didn't want that, we'd have just written:
+# from mercurial.commands import
+def findcmd(ui, cmd, table):
+ """Find and execute mercurial.*.findcmd(ui, cmd[, table])."""
+ try:
+ # ui argument was removed as of b4c035057d34
+ return findcmd.findcmd(cmd, table)
+ except TypeError:
+ try:
+ return findcmd.findcmd(ui, cmd, table)
+ except TypeError:
+ return findcmd.findcmd(ui, cmd)
+try:
+ findcmd.findcmd = cmdutil.findcmd
+ findcmd.__doc__ = cmdutil.findcmd.__doc__
+ findcmd.UnknownCommand = cmdutil.UnknownCommand
+except AttributeError:
+ findcmd.findcmd = commands.findcmd
+ findcmd.__doc__ = commands.findcmd.__doc__
+ findcmd.UnknownCommand = commands.UnknownCommand
+
+# For backwards compatibility, find the parseurl() function that splits
+# urls and revisions. Mercurial 0.9.3 doesn't have this, so we need
+# to provide a stub.
+try:
+ parseurl = cmdutil.parseurl
+except:
+ try:
+ parseurl = hg.parseurl
+ except:
+ def parseurl(url, revs):
+ """Mercurial <= 0.9.3 doesn't have this feature."""
+ return url, (revs or None)
+
+
+# For backwards compatibility, find the HTTP protocol.
+if not hasattr(hgweb, 'protocol'):
+ hgweb.protocol = hgweb.hgweb_mod.hgweb
+
+def cmd_options(ui, cmd, remove=None, table=commands.table):
+ aliases, spec = findcmd(ui, cmd, table)
+ res = list(spec[1])
+ if remove is not None:
+ res = [opt for opt in res
+ if opt[0] not in remove and opt[1] not in remove]
+ return res
+
+def walkhgenabled(ui, walkhg):
+ if not walkhg:
+ walkhg = ui.config('forest', 'walkhg', 'true')
+ try:
+ res = { '0' : False, 'false' : False, 'no' : False,
+ '1' : True, 'true' : True, 'yes' : True }[walkhg.lower()]
+ except KeyError:
+ raise util.Abort(_("invalid value for 'walkhg': %s" % walkhg))
+ return res
+
+def partialenabled(ui, partial):
+ if partial:
+ return partial
+ else:
+ partial = ui.config('forest', 'partial', 'false')
+ try:
+ res = { '0' : False, 'false' : False, 'no' : False,
+ '1' : True, 'true' : True, 'yes' : True }[partial.lower()]
+ except KeyError:
+ raise util.Abort(_("invalid value for 'partial': %s" % partial))
+ return res
+
+def _localrepo_forests(self, walkhg):
+ """Shim this function into mercurial.localrepo.localrepository so
+ that it gives you the list of subforests.
+
+ Return a list of roots in filesystem representation relative to
+ the self repository. This list is lexigraphically sorted.
+ """
+
+ def errhandler(err):
+ if err.filename == self.root:
+ raise err
+
+ def normpath(path):
+ if path:
+ return util.normpath(path)
+ else:
+ return '.'
+
+ res = {}
+ paths = [self.root]
+ while paths:
+ path = paths.pop()
+ if os.path.realpath(path) in res:
+ continue
+ for root, dirs, files in os.walk(path, onerror=errhandler):
+ hgdirs = dirs[:] # Shallow-copy to protect d from dirs.remove()
+ for d in hgdirs:
+ if d == '.hg':
+ res[os.path.realpath(root)] = root
+ if not walkhg:
+ dirs.remove(d)
+ else:
+ p = os.path.join(root, d)
+ if os.path.islink(p) and os.path.abspath(p) not in res:
+ paths.append(p)
+ res = res.values()
+ res.sort()
+ # Turn things into relative paths
+ pfx = len(self.root) + 1
+ return [normpath(r[pfx:]) for r in res]
+
+localrepo.localrepository.forests = _localrepo_forests
+
+
+def _sshrepo_forests(self, walkhg):
+ """Shim this function into mercurial.sshrepo.sshrepository so
+ that it gives you the list of subforests.
+
+ Return a list of roots as ssh:// URLs.
+ """
+
+ if 'forests' not in self.capabilities:
+ raise util.Abort(_("Remote forests cannot be cloned because the "
+ "other repository doesn't support the forest "
+ "extension."))
+ data = self.call("forests", walkhg=("", "True")[walkhg])
+ return data.splitlines()
+
+sshrepo.sshrepository.forests = _sshrepo_forests
+
+
+def _sshserver_do_hello(self):
+ '''the hello command returns a set of lines describing various
+ interesting things about the server, in an RFC822-like format.
+ Currently the only one defined is "capabilities", which
+ consists of a line in the form:
+
+ capabilities: space separated list of tokens
+ '''
+
+ caps = ['unbundle', 'lookup', 'changegroupsubset', 'forests']
+ if self.ui.configbool('server', 'uncompressed'):
+ if hasattr(self.repo, "revlogversion"):
+ version = self.repo.revlogversion
+ else:
+ version = self.repo.changelog.version
+ caps.append('stream=%d' % version)
+ self.respond("capabilities: %s\n" % (' '.join(caps),))
+
+sshserver.sshserver.do_hello = _sshserver_do_hello
+
+
+def _sshserver_do_forests(self):
+ """Shim this function into the sshserver so that it responds to
+ the forests command. It gives a list of roots relative to the
+ self.repo repository, sorted lexigraphically.
+ """
+
+ key, walkhg = self.getarg()
+ forests = self.repo.forests(bool(walkhg))
+ self.respond("\n".join(forests))
+
+sshserver.sshserver.do_forests = _sshserver_do_forests
+
+
+
+def _httprepo_forests(self, walkhg):
+ """Shim this function into mercurial.httprepo.httprepository so
+ that it gives you the list of subforests.
+
+ Return a list of roots as http:// URLs.
+ """
+
+ if 'forests' not in self.capabilities:
+ raise util.Abort(_("Remote forests cannot be cloned because the "
+ "other repository doesn't support the forest "
+ "extension."))
+ data = self.do_read("forests", walkhg=("", "True")[walkhg])
+ return data.splitlines()
+
+httprepo.httprepository.forests = _httprepo_forests
+
+
+def _httpserver_do_capabilities(self, req):
+ caps = ['lookup', 'changegroupsubset', 'forests']
+ if self.configbool('server', 'uncompressed'):
+ if hasattr(self.repo, "revlogversion"):
+ version = self.repo.revlogversion
+ else:
+ version = self.repo.changelog.version
+ caps.append('stream=%d' % version)
+ # XXX: make configurable and/or share code with do_unbundle:
+ unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
+ if unbundleversions:
+ caps.append('unbundle=%s' % ','.join(unbundleversions))
+ resp = ' '.join(caps)
+ req.httphdr("application/mercurial-0.1", length=len(resp))
+ req.write(resp)
+
+hgweb.protocol.do_capabilities = _httpserver_do_capabilities
+
+
+def _httpserver_do_forests(self, req):
+ """Shim this function into the httpserver so that it responds to
+ the forests command. It gives a list of roots relative to the
+ self.repo repository, sorted lexigraphically.
+ """
+
+ resp = ""
+ if req.form.has_key('walkhg'):
+ forests = self.repo.forests(bool(req.form['walkhg'][0]))
+ resp = "\n".join(forests)
+ req.httphdr("application/mercurial-0.1", length=len(resp))
+ req.write(resp)
+
+
+hgweb.protocol.do_forests = _httpserver_do_forests
+
+
+def _statichttprepo_forests(self, walkhg):
+ """Shim this function into
+ mercurial.statichttprepo.statichttprepository so that it gives you
+ the list of subforests.
+
+ It depends on the fact that most directory indices have directory
+ names followed by a slash. There is no reliable way of telling
+ whether a link leads into a subdirectory.
+
+ Return a list of roots in filesystem representation relative to
+ the self repository. This list is lexigraphically sorted.
+ """
+
+ import HTMLParser
+ import string
+ import urllib
+ import urllib2
+ import urlparse
+
+ class HtmlIndexParser(HTMLParser.HTMLParser):
+ def __init__(self, ui, paths, walkhg):
+ self._paths = paths
+ self._ui = ui
+ self._walkhg = walkhg
+ self.current = None
+ def handle_starttag(self, tag, attrs):
+ if string.lower(tag) == "a":
+ for attr in attrs:
+ if (string.lower(attr[0]) == "href" and
+ attr[1].endswith('/')):
+ link = urlparse.urlsplit(attr[1])
+ if (not self._walkhg and
+ link[2].rstrip('/').split('/')[-1] == '.hg'):
+ break
+ if not link[0] and not link[2].startswith('/'):
+ self._ui.debug(_("matched on '%s'") % attr[1])
+ self._paths.append(urlparse.urljoin(self.current,
+ attr[1]))
+
+ if self._url.endswith('/'):
+ url = self._url
+ else:
+ url = self._url + '/'
+
+ res = []
+ paths = [url]
+ seen = {}
+
+ parser = HtmlIndexParser(self.ui, paths, walkhg)
+ while paths:
+ path = paths.pop()
+ if not seen.has_key(path):
+ seen[path] = True
+ parser.current = path
+ index = None
+ try:
+ self.ui.debug(_("retrieving '%s'\n") % path)
+ index = urllib2.urlopen(path)
+ parser.reset()
+ parser.feed(index.read())
+ parser.close()
+ hg_path = urlparse.urljoin(path, '.hg')
+ self.ui.debug(_("retrieving '%s'\n") % hg_path)
+ hg = urllib2.urlopen(hg_path)
+ res.append(path)
+ except urllib2.HTTPError, inst:
+ pass
+ #raise IOError(None, inst)
+ except urllib2.URLError, inst:
+ pass
+ #raise IOError(None, inst.reason[1])
+
+ res.sort()
+ # Turn things into relative paths
+ return [root[len(url):].rstrip('/') or "." for root in res]
+
+statichttprepo.statichttprepository.forests = _statichttprepo_forests
+
+
+def die_on_numeric_revs(revs):
+ """Check to ensure that the revs passed in are not numeric.
+
+ Numeric revisions make no sense when searching a forest. You want
+ only named branches and tags. The only special exception is
+ revision -1, which occurs before the first checkin.
+ """
+ if revs is None:
+ return
+ for strrev in revs:
+ try:
+ intrev = int(strrev)
+ except:
+ continue # String-based revision
+ if intrev == 0 and strrev.startswith("00"):
+ continue # Revision -1
+ raise util.Abort(_("numeric revision '%s'") % strrev)
+
+
+def relpath(root, pathname):
+ """Returns the relative path of a local pathname from a local root."""
+ root = os.path.abspath(root)
+ pathname = os.path.abspath(pathname)
+ if root == pathname or pathname.startswith(root + os.sep):
+ pathname = os.path.normpath(pathname[len(root)+1:])
+ return pathname
+
+
+def urltopath(url):
+ if url and hg.islocal(url):
+ if url.startswith("file://"):
+ url = url[7:]
+ elif url.startswith("file:"):
+ url = url[5:]
+ return url
+
+
+class Forest(object):
+ """Describes the state of the forest within the current repository.
+
+ This data structure describes the Forest contained within the
+ current repository. It contains a list of Trees that describe
+ each sub-repository.
+ """
+
+ class SnapshotError(ConfigParser.NoSectionError):
+ pass
+
+ class Tree(object):
+ """Describe a local sub-repository within a forest."""
+
+ class Skip(Warning):
+ """Exception that signals this tree should be skipped."""
+ pass
+
+ __slots__ = ('_repo', '_root', 'revs', 'paths')
+
+ def __init__(self, repo=None, root=None, revs=[], paths={}):
+ """Create a Tree object.
+
+ repo may be any mercurial.localrepo object
+ root is the absolute path of this repo object
+ rev is the desired revision for this repository, None meaning the tip
+ paths is a dictionary of path aliases to real paths
+ """
+ self._repo = repo
+ self.revs = revs
+ if repo:
+ self.paths = {}
+ self.setrepo(repo)
+ self.paths.update(paths)
+ else:
+ self.setroot(root)
+ self.paths = paths
+
+ def die_on_mq(self, rootpath=None):
+ """Raises a util.Abort exception if self has mq patches applied."""
+ if self.mq_applied():
+ rpath = self.root
+ if rootpath:
+ if not isinstance(rootpath, str):
+ rootpath = rootpath.root
+ rpath = relpath(rootpath, rpath)
+ raise util.Abort(_("'%s' has mq patches applied") % rpath)
+
+ def mq_applied(self):
+ rpath = urltopath(self.root)
+ if not hg.islocal(rpath):
+ raise util.Abort(_("'%s' is not a local repository") % rpath)
+ rpath = util.localpath(rpath)
+ rpath = os.path.join(rpath, ".hg")
+ if not os.path.isdir(rpath):
+ return False
+ for entry in os.listdir(rpath):
+ path = os.path.join(rpath, entry)
+ if (os.path.isdir(path) and
+ os.path.isfile(os.path.join(path, 'series'))):
+ try:
+ s = os.stat(os.path.join(path, "status"))
+ if s.st_size > 0:
+ return path
+ except OSError, err:
+ if err.errno != errno.ENOENT:
+ raise
+ return False
+
+ def getpath(self, paths):
+ assert(type(paths) != str)
+ if paths is None:
+ return None
+ for path in paths:
+ if not hg.islocal(path):
+ return path
+ result = urltopath(path)
+ if os.path.isdir(result):
+ return result
+ result = urltopath(self.paths.get(path, None))
+ if result is not None:
+ return result
+ return None
+
+ def getrepo(self, ui=False):
+ if not self._repo and self._root:
+ if ui is False:
+ raise AttributeError("getrepo() requires 'ui' parameter")
+ self._repo = hg.repository(ui, self._root)
+ return self._repo
+
+ def setrepo(self, repo):
+ self._root = None
+ self._repo = repo
+ if repo.ui:
+ self.paths.update(dict(repo.ui.configitems('paths')))
+
+ def getroot(self):
+ if self._repo:
+ return self._repo.root
+ else:
+ return self._root
+
+ def setroot(self, root):
+ self._repo = None
+ self._root = root
+
+ @staticmethod
+ def skip(function):
+ """Decorator that turns any exception into a Forest.Tree.Skip"""
+ def skipme(*args, **keywords):
+ try:
+ function(*args, **keywords)
+ except Exception, err:
+ raise Forest.Tree.Skip(err)
+ return skipme
+
+ @staticmethod
+ def warn(function):
+ """Decorator that turns any exception into a Warning"""
+ def warnme(*args, **keywords):
+ try:
+ function(*args, **keywords)
+ except Exception, err:
+ raise Warning(err)
+ return warnme
+
+ def working_revs(self):
+ """Returns the revision of the working copy."""
+ try:
+ ctx = self.repo[None]
+ except TypeError:
+ ctx = self.repo.workingctx()
+ parents = ctx.parents()
+ return [node.hex(parents[0].node())]
+
+ def __repr__(self):
+ return ("<forest.Tree object "
+ "- repo: %s "
+ "- revs: %s "
+ "- root: %s "
+ "- paths: %s>") % (self.repo, self.revs,
+ self.root, self.paths)
+
+ repo = property(getrepo, setrepo, None, None)
+ root = property(getroot, setroot, None, None)
+
+ __slots__ = ('trees', 'snapfile')
+
+ def __init__(self, error=None, top=None, snapfile=None, walkhg=True):
+ """Create a Forest object.
+
+ top is the mercurial.localrepo object at the top of the forest.
+ snapfile is the filename of the snapshot file.
+ walkhg controls if we descend into .hg directories.
+
+ If you provide no snapfile, the top repo will be searched for
+ sub-repositories.
+
+ If you do provide a snapfile, then the snapfile will be read
+ for sub-repositories and no searching of the filesystem will
+ be done. The top repository is queried for the root of all
+ relative paths, but if it's missing, then the current
+ directory will be assumed.
+ """
+ if error:
+ raise AttributeError("__init__() takes only named arguments")
+ self.trees = []
+ self.snapfile = None
+ if snapfile:
+ self.snapfile = snapfile
+ if top is None:
+ toppath = ""
+ else:
+ toppath = top.root
+ self.read(snapfile, toppath)
+ elif top:
+ self.trees.append(Forest.Tree(repo=top))
+ if top.ui:
+ top.ui.note(_("searching for repos in %s\n") % top.root)
+ self.scan(walkhg)
+
+ def collate_files(self, pats):
+ """Returns a dictionary of absolute file paths, keyed Tree.
+
+ This lets us iterate over repositories with only the files
+ that belong to them.
+ """
+ result = {}
+ files = [os.path.abspath(path) for path in list(pats)]
+ if files:
+ files.sort(reverse=True)
+ trees = self.trees[:]
+ trees.sort(reverse=True, key=(lambda tree: tree.root))
+ for tree in trees:
+ paths = []
+ for path in files[:]:
+ if not os.path.exists(path):
+ raise util.Abort(_("%s not under root") % path)
+ if path.startswith(tree.root):
+ paths.append(path)
+ files.remove(path)
+ if paths:
+ result[tree] = paths
+ return result
+
+
+ def apply(self, ui, function, paths, opts, prehooks=[]):
+ """Apply function(repo, targetpath, opts) to the entire forest.
+
+ path is a path provided on the command line.
+ function is a function that should be called for every repository.
+ opts is a list of options provided to the function
+ prehooks is a list of hook(tree) that are run before function()
+
+ Useful for the vast majority of commands that scan a local
+ forest and perform some command on each sub-repository.
+
+ Skips a sub-repository skipping it isn't actually a repository
+ or if it has mq patches applied.
+
+ In function(), targetpath will be /-separated. You may have
+ to util.localpath() it.
+ """
+ opts['force'] = None # Acting on unrelated repos is BAD
+ if paths:
+ # Extract revisions from # syntax in path.
+ paths[0], revs = parseurl(paths[0], opts['rev'])[0:2]
+ elif 'rev' in opts:
+ revs = opts['rev']
+ else:
+ revs = None
+ die_on_numeric_revs(revs)
+ for tree in self.trees:
+ rpath = relpath(self.top().root, tree.root)
+ ui.status("[%s]\n" % rpath)
+ try:
+ for hook in prehooks:
+ try:
+ hook(tree)
+ except Forest.Tree.Skip:
+ raise
+ except Warning, message:
+ ui.warn(_("warning: %s\n") % message)
+ except Forest.Tree.Skip, message:
+ ui.warn(_("skipped: %s\n") % message)
+ ui.status("\n")
+ continue
+ except util.Abort:
+ raise
+ if revs:
+ opts['rev'] = revs
+ else:
+ opts['rev'] = tree.revs
+ targetpath = paths or None
+ if paths:
+ targetpath = tree.getpath(paths)
+ if targetpath:
+ if targetpath == paths[0] and rpath != os.curdir:
+ targetpath = '/'.join((targetpath, util.pconvert(rpath)))
+ function(tree, targetpath, opts)
+ ui.status("\n")
+
+ def read(self, snapfile, toppath="."):
+ """Loads the information in snapfile into this forest.
+
+ snapfile is the filename of a snapshot file
+ toppath is the path of the top of this forest
+ """
+ if not toppath:
+ toppath = "."
+ cfg = ConfigParser.RawConfigParser()
+ if not cfg.read([snapfile]):
+ raise util.Abort("%s: %s" % (snapfile, os.strerror(errno.ENOENT)))
+ seen_root = False
+ sections = {}
+ for section in cfg.sections():
+ if section.endswith('.paths'):
+ # Compatibility with old Forest snapshot files
+ paths = dict(cfg.items(section))
+ section = section[:-6]
+ if section in sections:
+ sections[section].paths.update(paths)
+ else:
+ sections[section] = Forest.Tree(paths=paths)
+ else:
+ root = cfg.get(section, 'root')
+ if root == '.':
+ seen_root = True
+ root = toppath
+ else:
+ root = os.path.join(toppath, util.localpath(root))
+ root = os.path.normpath(root)
+ rev = cfg.get(section, 'revision')
+ if not rev:
+ rev = []
+ paths = dict([(k[5:], v)
+ for k, v in cfg.items(section)
+ if k.startswith('path')])
+ if section in sections:
+ sections[section].root = root
+ sections[section].revs = [rev]
+ sections[section].paths.update(paths)
+ else:
+ sections[section] = Forest.Tree(root=root,
+ revs=[rev],
+ paths=paths)
+ if not seen_root:
+ raise Forest.SnapshotError("Could not find 'root = .' in '%s'" %
+ snapfile)
+ self.trees = sections.values()
+ self.trees.sort(key=(lambda tree: tree.root))
+
+ def scan(self, walkhg):
+ """Scans for sub-repositories within this forest.
+
+ This method modifies this forest in-place. It searches within the
+ forest's directories and enumerates all the repositories it finds.
+ """
+ trees = []
+ top = self.top()
+ ui = top.repo.ui
+ for relpath in top.repo.forests(walkhg):
+ if relpath != '.':
+ abspath = os.path.join(top.root, util.localpath(relpath))
+ trees.append(Forest.Tree(hg.repository(ui, abspath)))
+ trees.sort(key=(lambda tree: tree.root))
+ trees.insert(0, Forest.Tree(hg.repository(ui, top.root)))
+ self.trees = trees
+
+ def top(self):
+ """Returns the top Forest.Tree in this forest."""
+ if len(self.trees):
+ return self.trees[0]
+ else:
+ return None
+
+ def update(self, ui=None):
+ """Gets the most recent information about repos."""
+ try:
+ if not ui:
+ ui = self.top().repo.ui
+ except:
+ pass
+ for tree in self.trees:
+ try:
+ repo = hg.repository(ui, tree.root)
+ except RepoError:
+ repo = None
+ tree.repo = repo
+
+ def write(self, fd, oldstyle=False):
+ """Writes a snapshot file to a file descriptor."""
+ counter = 1
+ for tree in self.trees:
+ fd.write("[tree%s]\n" % counter)
+ root = relpath(self.top().root, tree.root)
+ if root == os.curdir:
+ root = '.'
+ root = util.normpath(root)
+ fd.write("root = %s\n" % root)
+ if tree.revs:
+ fd.write("revision = %s\n" % tree.revs[0])
+ else:
+ fd.write("revision = None\n")
+ if not oldstyle:
+ for name, path in tree.paths.items():
+ fd.write("path.%s = %s\n" % (name, path))
+ else:
+ fd.write("\n[tree%s.paths]\n" % counter)
+ for name, path in tree.paths.items():
+ fd.write("%s = %s\n" % (name, path))
+ fd.write("\n")
+ counter += 1
+
+
+ def __repr__(self):
+ return ("<forest.Forest object - trees: %s> ") % self.trees
+
+
+def qclone(ui, source, sroot, dest, rpath, opts):
+ """Helper function to clone from a remote repository.
+
+ source is the URL of the source of this repository
+ dest is the directory of the destination
+ rpath is the relative path of the destination
+ opts are a list of options to be passed into the clone
+ """
+ ui.status("[%s]\n" % rpath)
+ assert(dest is not None)
+ destpfx = os.path.normpath(os.path.dirname(dest))
+ if not os.path.exists(destpfx):
+ os.makedirs(destpfx)
+ repo = hg.repository(ui, source)
+ mqdir = None
+ assert(source is not None)
+ if hg.islocal(source):
+ Forest.Tree(repo=repo).die_on_mq(sroot)
+ url = urltopath(repo.url())
+ ui.note(_("cloning %s to %s\n") % (url, dest))
+ commands.clone(ui, url, dest, **opts)
+ repo = None
+
+
+def clone(ui, source, dest=None, **opts):
+ """make a clone of an existing forest of repositories
+
+ Create a clone of an existing forest in a new directory.
+
+ Look at the help text for the clone command for more information.
+ """
+ die_on_numeric_revs(opts['rev'])
+ source = ui.expandpath(source) or source
+ islocalsrc = hg.islocal(source)
+ if islocalsrc:
+ source = os.path.abspath(urltopath(source))
+ if dest:
+ if hg.islocal(dest):
+ dest = os.path.normpath(dest)
+ else:
+ pass
+ else:
+ dest = hg.defaultdest(source)
+ toprepo = hg.repository(ui, source)
+ forests = toprepo.forests(walkhgenabled(ui, opts['walkhg']))
+ for rpath in forests:
+ if rpath == '.':
+ rpath = ''
+ if islocalsrc:
+ srcpath = source
+ srcpath = os.path.join(source, util.localpath(rpath))
+ else:
+ srcpath = '/'.join((source, rpath))
+ if rpath:
+ destpath = os.path.join(dest, util.localpath(rpath))
+ else:
+ destpath = dest
+ try:
+ qclone(ui=ui,
+ source=srcpath, sroot=source,
+ dest=destpath, rpath=os.path.normpath(rpath),
+ opts=opts)
+ except util.Abort, err:
+ ui.warn(_("skipped: %s\n") % err)
+ ui.status("\n")
+
+
+def fetch(ui, top, source="default", **opts):
+ """pull changes from a remote forest, merge new changes if needed.
+
+ This finds all changes from the forest at the specified path or
+ URL and adds them to the local forest.
+
+ Look at the help text for the fetch command for more information.
+ """
+
+ snapfile = opts['snapfile']
+ forest = Forest(top=top, snapfile=snapfile,
+ walkhg=walkhgenabled(ui, opts['walkhg']))
+ source = [source]
+ try:
+ import hgext.fetch as fetch
+ except ImportError:
+ raise util.Abort(_("could not import fetch module\n"))
+
+ def function(tree, srcpath, opts):
+ if not srcpath:
+ srcpath = forest.top().getpath(source)
+ if srcpath:
+ rpath = util.pconvert(relpath(forest.top().root, tree.root))
+ srcpath = '/'.join((srcpath, rpath))
+ else:
+ ui.warn(_("skipped: %s\n") %
+ _("repository %s not found") % source[0])
+ return
+ try:
+ fetch.fetch(ui, tree.getrepo(ui), srcpath, **opts)
+ except Exception, err:
+ ui.warn(_("skipped: %s\n") % err)
+ try:
+ tree.repo.transaction().__del__()
+ except AttributeError:
+ pass
+
+ @Forest.Tree.skip
+ def check_mq(tree):
+ tree.die_on_mq(top.root)
+
+ forest.apply(ui, function, source, opts,
+ prehooks=[lambda tree: check_mq(tree)])
+
+
+def incoming(ui, top, source="default", **opts):
+ """show new changesets found in source forest
+
+ Show new changesets found in the specified path/URL or the default
+ pull location for each repository in the source forest.
+
+ Look at the help text for the incoming command for more information.
+ """
+ die_on_numeric_revs(opts['rev'])
+ forest = Forest(top=top, snapfile=opts['snapfile'],
+ walkhg=walkhgenabled(ui, opts['walkhg']))
+ source = [source]
+ opts["bundle"] = ""
+
+ def function(tree, srcpath, opts):
+ if not srcpath:
+ srcpath = forest.top().getpath(source)
+ if srcpath:
+ rpath = util.pconvert(relpath(forest.top().root, tree.root))
+ srcpath = '/'.join((srcpath, rpath))
+ else:
+ ui.warn(_("skipped: %s\n") %
+ _("repository %s not found") % source[0])
+ return
+ try:
+ commands.incoming(ui, tree.repo, srcpath, **opts)
+ except Exception, err:
+ ui.warn(_("skipped: %s\n") % err)
+
+ @Forest.Tree.warn
+ def check_mq(tree):
+ tree.die_on_mq(top.root)
+
+ forest.apply(ui, function, source, opts,
+ prehooks=[lambda tree: check_mq(tree)])
+
+
+def outgoing(ui, top, dest=None, **opts):
+ """show changesets not found in destination forest
+
+ Show changesets not found in the specified destination forest or
+ the default push location.
+
+ Look at the help text for the outgoing command for more information.
+ """
+ die_on_numeric_revs(opts['rev'])
+ forest = Forest(top=top, snapfile=opts['snapfile'],
+ walkhg=walkhgenabled(ui, opts['walkhg']))
+ if dest == None:
+ dest = ["default-push", "default"]
+ else:
+ dest = [dest]
+
+ def function(tree, destpath, opts):
+ if not destpath:
+ destpath = forest.top().getpath(dest)
+ if destpath:
+ rpath = util.pconvert(relpath(forest.top().root, tree.root))
+ destpath = '/'.join((destpath, rpath))
+ else:
+ ui.warn(_("skipped: %s\n") %
+ _("repository %s not found") % dest[0])
+ return
+ try:
+ commands.outgoing(ui, tree.repo, destpath, **opts)
+ except Exception, err:
+ ui.warn(_("skipped: %s\n") % err)
+
+ @Forest.Tree.warn
+ def check_mq(tree):
+ tree.die_on_mq(top.root)
+
+ forest.apply(ui, function, dest, opts,
+ prehooks=[lambda tree: check_mq(tree)])
+
+
+def pull(ui, top, source="default", pathalias=None, **opts):
+ """pull changes from the specified forest
+
+ Pull changes from a remote forest to a local one.
+
+ You may specify a snapshot file, which is generated by the fsnap
+ command. For each tree in this file, pull the specified revision
+ from the specified source path.
+
+ By default, pull new remote repositories that it discovers. If
+ you use the -p option, pull only the repositories available locally.
+
+ Look at the help text for the pull command for more information.
+ """
+
+ die_on_numeric_revs(opts['rev'])
+ if pathalias:
+ # Compatibility with old 'hg fpull SNAPFILE PATH-ALIAS' syntax
+ snapfile = source
+ source = pathalias
+ else:
+ snapfile = opts['snapfile']
+ source = [source]
+ walkhg = walkhgenabled(ui, opts['walkhg'])
+ forest = Forest(top=top, snapfile=snapfile, walkhg=walkhg)
+ toproot = forest.top().root
+ if not snapfile:
+ # Look for new remote paths from source
+ srcpath = forest.top().getpath(source) or ""
+ srcrepo = hg.repository(ui, srcpath)
+ srcforests = None
+ try:
+ srcforests = srcrepo.forests(walkhg)
+ except util.Abort, err:
+ ui.note(_("skipped new forests: %s\n") % err)
+ if srcforests:
+ ui.note(_("looking for new forests\n"))
+ newrepos = [util.localpath(root) for root in srcforests]
+ for tree in forest.trees:
+ try:
+ newrepos.remove(relpath(toproot, tree.root))
+ except Exception, err:
+ pass
+ ui.note(_("found new forests: %s\n") % newrepos)
+ forest.trees.extend([Forest.Tree(root=os.path.join(toproot, new))
+ for new in newrepos])
+ forest.trees.sort(key=(lambda tree: tree.root))
+ opts['pull'] = True
+ opts['uncompressed'] = None
+ opts['noupdate'] = not opts['update']
+ partial = partialenabled(ui, opts['partial'])
+
+ def function(tree, srcpath, opts):
+ if snapfile:
+ opts['rev'] = tree.revs
+ else:
+ destpath = relpath(os.path.abspath(os.curdir), tree.root)
+ rpath = util.pconvert(relpath(toproot, tree.root))
+ if not srcpath:
+ srcpath = forest.top().getpath(source)
+ if srcpath:
+ srcpath = '/'.join((srcpath, rpath))
+ else:
+ ui.warn(_("warning: %s\n") %
+ _("repository %s not found") % source[0])
+ return
+ try:
+ tree.getrepo(ui)
+ except RepoError:
+ if partial:
+ ui.warn(_("skipped: new remote repository\n"))
+ else:
+ # Need to clone
+ quiet = ui.quiet
+ try:
+ ui.quiet = True # Hack to shut up qclone's ui.status()
+ qclone(ui=ui,
+ source=srcpath, sroot=source,
+ dest=destpath, rpath=rpath,
+ opts=opts)
+ except util.Abort, err:
+ ui.warn(_("skipped: %s\n") % err)
+ ui.quiet = quiet
+ return
+ try:
+ commands.pull(ui, tree.getrepo(ui), srcpath, **opts)
+ except Exception, err:
+ ui.warn(_("skipped: %s\n") % err)
+ if tree._repo:
+ tree.repo.transaction().__del__()
+
+ @Forest.Tree.skip
+ def check_mq(tree):
+ tree.die_on_mq(top.root)
+
+ forest.apply(ui, function, source, opts,
+ prehooks=[lambda tree: check_mq(tree)])
+
+def push(ui, top, dest=None, pathalias=None, **opts):
+ """push changes to the specified forest.
+
+ Push changes from the local forest to the given destination.
+
+ You may specify a snapshot file, which is generated by the fsnap
+ command. For each tree in this file, push the specified revision
+ to the specified destination path.
+
+ Look at the help text for the push command for more information.
+ """
+
+ if pathalias:
+ # Compatibility with old 'hg fpush SNAPFILE PATH-ALIAS' syntax
+ snapfile = dest
+ dest = [pathalias]
+ opts['rev'] = ['tip'] # Force a push from tip
+ else:
+ snapfile = opts['snapfile']
+ if dest:
+ dest = [dest]
+ else:
+ dest = ["default-push", "default"]
+ forest = Forest(top=top, snapfile=snapfile,
+ walkhg=walkhgenabled(ui, opts['walkhg']))
+
+ def function(tree, destpath, opts):
+ try:
+ commands.push(ui, tree.getrepo(ui), destpath, **opts)
+ except Exception, err:
+ ui.warn(_("skipped: %s\n") % err)
+ try:
+ tree.repo.transaction().__del__()
+ except AttributeError:
+ pass
+
+ @Forest.Tree.skip
+ def check_mq(tree):
+ tree.die_on_mq(top.root)
+
+ forest.apply(ui, function, dest, opts,
+ prehooks=[lambda tree: check_mq(tree)])
+
+
+def seed(ui, snapshot=None, source='default', **opts):
+ """populate a forest according to a snapshot file.
+
+ Populate an empty local forest according to a snapshot file.
+
+ Given a snapshot file, clone any non-existant directory from the
+ provided path-alias. This defaults to cloning from the 'default'
+ path.
+
+ Unless the --tip option is set, this command will clone the
+ revision specified in the snapshot file.
+
+ Look at the help text for the clone command for more information.
+ """
+
+ snapfile = snapshot or opts['snapfile']
+ if not snapfile:
+ raise cmdutil.ParseError("fseed", _("invalid arguments"))
+ forest = Forest(snapfile=snapfile)
+ tip = opts['tip']
+ dest = opts['root']
+ if not dest:
+ dest = os.curdir
+ forest.trees.remove(forest.top())
+ dest = os.path.normpath(dest)
+ for tree in forest.trees:
+ srcpath = tree.getpath([source])
+ if not srcpath:
+ ui.status("[%s]\n" % util.pconvert(tree.root))
+ ui.warn(_("skipped: path alias %s not defined\n") % source)
+ ui.status("\n")
+ continue
+ srcpath = urltopath(srcpath)
+ if tree.root == ".":
+ destpath = dest
+ else:
+ destpath = os.path.join(dest, tree.root)
+ opts['rev'] = tree.revs
+ try:
+ qclone(ui=ui,
+ source=srcpath, sroot=None,
+ dest=destpath, rpath=util.pconvert(tree.root),
+ opts=opts)
+ except util.Abort, err:
+ ui.warn(_("skipped: %s\n") % err)
+ ui.status("\n")
+
+
+def snap(ui, top, snapshot=None, **opts):
+ """take a snapshot of the forest and show it
+
+ Shows the current state of the forest.
+
+ You can use the output of this command as with the --snapfile
+ option of other forest commands.
+
+ When you provide a snapshot file, only the trees mentioned in that
+ file will be shown.
+ """
+
+ snapfile = snapshot or opts['snapfile']
+ tip = opts['tip']
+ forest = Forest(top=top, snapfile=snapfile,
+ walkhg=walkhgenabled(ui, opts['walkhg']))
+ if snapfile:
+ forest.update(ui)
+ for tree in forest.trees:
+ tree.die_on_mq(top.root)
+ if not tip:
+ tree.revs = tree.working_revs()
+ forest.write(ui, opts['compatible'])
+
+
+def status(ui, top, *pats, **opts):
+ """show changed files in the working forest
+
+ Show status of files in this forest's repositories.
+
+ Look at the help text for the status command for more information.
+ """
+ forest = Forest(top=top, walkhg=walkhgenabled(ui, opts['walkhg']))
+ die_on_numeric_revs(opts['rev'])
+ # Figure out which paths are relative to which roots
+ files = forest.collate_files(pats)
+ if files:
+ # Trim which trees we're going to look at
+ forest.trees = files.keys()
+
+ class munge_ui(object):
+ """This wrapper class allows us to munge the mercurial.ui.write() """
+ def __init__(self, transform, ui):
+ self._transform = transform
+ self._ui = ui
+ def write(self, output):
+ self._ui.write(self._transform(output))
+ def __getattr__(self, attrname):
+ return getattr(self._ui, attrname)
+
+ def function(tree, path, opts):
+ path = util.localpath(path)
+ if files:
+ pats = files[tree]
+ else:
+ pats = ()
+ if path == top.root:
+ path = ''
+ else:
+ path = relpath(top.root, path)
+ def prefix(output):
+ """This function shims the root in before the filename."""
+ if opts['no_status']:
+ return os.path.join(path, output)
+ else:
+ prefix, filename = output.split(' ', 1)
+ return ' '.join((prefix, os.path.join(path, filename)))
+ localui = munge_ui(prefix, ui)
+ try:
+ commands.status(localui, tree.repo, *pats, **opts)
+ except RepoError, err:
+ ui.warn(_("skipped: %s\n") % err)
+
+ @Forest.Tree.warn
+ def check_mq(tree):
+ tree.die_on_mq(top.root)
+
+ forest.apply(ui, function, [top.root], opts,
+ prehooks=[lambda tree: check_mq(tree)])
+
+
+def trees(ui, top, **opts):
+ """show the roots of the repositories
+
+ Show the roots of the trees in the forest.
+
+ By default, show the absolute path of each repository. With
+ --convert, show the portable Mercurial path.
+ """
+
+ forest = Forest(top=top,
+ walkhg=walkhgenabled(ui, opts['walkhg']))
+ convert = opts['convert']
+ for tree in forest.trees:
+ if convert:
+ ui.write("%s\n" % relpath(top.root, tree.root))
+ else:
+ ui.write("%s\n" % util.localpath(tree.root))
+
+
+def update(ui, top, revision=None, **opts):
+ """update working forest
+
+ Update the working forest to the specified revision, or the
+ tip of the current branch if none is specified.
+
+ You may specify a snapshot file, which is generated by the fsnap
+ command. For each tree in this file, update to the revision
+ recorded for that tree.
+
+ Look at the help text for the update command for more information.
+ """
+
+ snapfile = None
+ if revision:
+ cp = ConfigParser.RawConfigParser()
+ try:
+ if cp.read([revision]):
+ # Compatibility with old 'hg fupdate SNAPFILE' syntax
+ snapfile = revision
+ except Exception, err:
+ if isinstance(err, ConfigParser.Error):
+ ui.warn(_("warning: %s\n") % err)
+ else:
+ raise err
+ snapfile = opts['snapfile']
+ opts['rev'] = revision
+ tip = opts['tip']
+ forest = Forest(top=top, snapfile=snapfile,
+ walkhg=walkhgenabled(ui, opts['walkhg']))
+
+ def function(tree, ignore, opts):
+ if 'rev' in opts:
+ rev = opts['rev'] or None
+ else:
+ rev = None
+ if type(rev) is str:
+ rev = rev
+ elif rev:
+ rev = rev[0]
+ try:
+ if rev is not None:
+ commands.update(ui, tree.getrepo(ui),
+ rev=rev, clean=opts['clean'], date=opts['date'])
+ else:
+ commands.update(ui, tree.getrepo(ui),
+ clean=opts['clean'], date=opts['date'])
+ except Exception, err:
+ ui.warn(_("skipped: %s\n") % err)
+ tree.repo.transaction().__del__()
+
+ @Forest.Tree.skip
+ def check_mq(tree):
+ tree.die_on_mq(top.root)
+
+ forest.apply(ui, function, None, opts,
+ prehooks=[lambda tree: check_mq(tree)])
+
+
+cmdtable = None
+
+def uisetup(ui):
+ global cmdtable
+ walkhgopts = ('', 'walkhg', '',
+ _("walk repositories under '.hg' (yes/no)"))
+ snapfileopts = ('', 'snapfile', '',
+ _("snapshot file generated by fsnap"))
+ cmdtable = {
+ "^fclone" :
+ (clone,
+ [walkhgopts] + cmd_options(ui, 'clone'),
+ _('hg fclone [OPTION]... SOURCE [DEST]')),
+ "fincoming|fin" :
+ (incoming,
+ [walkhgopts, snapfileopts]
+ + cmd_options(ui, 'incoming', remove=('f', 'bundle')),
+ _('hg fincoming [OPTION]... [SOURCE]')),
+ "foutgoing|fout" :
+ (outgoing,
+ [walkhgopts, snapfileopts]
+ + cmd_options(ui, 'outgoing', remove=('f',)),
+ _('hg foutgoing [OPTION]... [DEST]')),
+ "^fpull" :
+ (pull,
+ [('p', 'partial', False,
+ _("do not pull new remote repositories")),
+ walkhgopts, snapfileopts] + cmd_options(ui, 'pull', remove=('f',)),
+ _('hg fpull [OPTION]... [SOURCE]')),
+ "^fpush" :
+ (push,
+ [walkhgopts, snapfileopts] + cmd_options(ui, 'push', remove=('f',)),
+ _('hg fpush [OPTION]... [DEST]')),
+ "fseed" :
+ (seed,
+ [('', 'root', '',
+ _("create root as well as children under <root>")),
+ snapfileopts,
+ ('t', 'tip', False,
+ _("use tip instead of revisions stored in the snapshot file"))]
+ + cmd_options(ui, 'clone', remove=('r',)),
+ _('hg fseed [OPTION]... SNAPSHOT-FILE [PATH-ALIAS]')),
+ "fsnap" :
+ (snap,
+ [('', 'compatible', False,
+ _("write snapshot file compatible with older forest versions")),
+ snapfileopts,
+ ('t', 'tip', False,
+ _("record tip instead of actual child revisions")),
+ walkhgopts],
+ _('hg fsnap [OPTION]... [SNAPSHOT-FILE]')),
+ "^fstatus|fst" :
+ (status,
+ [walkhgopts] + cmd_options(ui, 'status'),
+ _('hg fstatus [OPTION]... [FILE]...')),
+ "ftrees" :
+ (trees,
+ [('c', 'convert', False,
+ _("convert paths to mercurial representation")),
+ walkhgopts],
+ _('hg ftrees [OPTIONS]')),
+ "^fupdate|fup|fcheckout|fco" :
+ (update,
+ [snapfileopts,
+ ('', 'tip', False,
+ _("use tip instead of revisions stored in the snapshot file")),
+ walkhgopts]
+ + cmd_options(ui, 'update'),
+ _('hg fupdate [OPTION]...'))
+ }
+
+ try:
+ import hgext.fetch
+ except ImportError:
+ return
+ try:
+ cmdtable.update({"ffetch": (fetch,
+ [walkhgopts, snapfileopts]
+ + cmd_options(ui, 'fetch',
+ remove=('bundle',),
+ table=hgext.fetch.cmdtable),
+ _('hg ffetch [OPTION]... [SOURCE]'))})
+ except findcmd.UnknownCommand:
+ return
+
+commands.norepo += " fclone fseed"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macports-changes/attachments/20081116/48c2ed84/attachment-0001.html>
More information about the macports-changes
mailing list