[cig-commits] commit: forest extension version 0.1
Mercurial
hg at geodynamics.org
Mon Nov 24 11:26:45 PST 2008
changeset: 0:c499a5ffd657
user: Robin Farine <robin.farine at terminus.org>
date: Sat Jul 15 02:44:21 2006 +0200
files: .hgignore forest.py
description:
forest extension version 0.1
diff -r 000000000000 -r c499a5ffd657 .hgignore
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Sat Jul 15 02:44:21 2006 +0200
@@ -0,0 +1,4 @@
+syntax: glob
+
+*~
+*.pyc
diff -r 000000000000 -r c499a5ffd657 forest.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/forest.py Sat Jul 15 02:44:21 2006 +0200
@@ -0,0 +1,361 @@
+# 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.
+
+"""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 'forest-snapshot' 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.
+"""
+
+version = "0.1"
+
+import ConfigParser
+import os
+import sys
+
+import mercurial.node
+from mercurial import commands, util
+
+try: # 'find' renamed as 'findcmd' after Mercurial 0.9
+ from mercurial.commands import findcmd
+except:
+ from mercurial.commands import find as findcmd
+from mercurial.hg import repository
+from mercurial.i18n import gettext as _
+from mercurial.repo import RepoError
+
+commands.norepo += " forest-clone"
+
+
+def cmd_options(cmd, remove=None):
+ aliases, spec = findcmd(cmd)
+ res = list(spec[1])
+ if remove is not None:
+ res = [opt for opt in res if opt[0] not in remove]
+ return res
+
+
+def enumerate_repos(top=''):
+ """Generate a lexicographically sorted list of repository roots."""
+
+ dirs = ['.']
+ while dirs:
+ root = dirs.pop()
+ entries = os.listdir(os.path.join(top, root))
+ entries.sort(reverse=True)
+ for e in entries:
+ path = os.path.join(root, e)
+ if not os.path.isdir(os.path.join(top, path)):
+ continue
+ if e == '.hg':
+ yield util.normpath(root)
+ else:
+ dirs.append(path)
+
+
+def mq_patches_applied(rootpath):
+ rootpath = os.path.join(rootpath, ".hg")
+ entries = os.listdir(rootpath)
+ for e in entries:
+ path = os.path.join(rootpath, e)
+ if e == "data" or not os.path.isdir(path):
+ continue
+ series = os.path.join(path, "series")
+ status = os.path.join(path, "status")
+ if os.path.isfile(series):
+ s = os.stat(status)
+ if s.st_size > 0:
+ return True
+ return False
+
+
+class ForestSnapshot(object):
+
+ class Tree(object):
+
+ __slots__ = ('root', 'rev', 'paths')
+
+ def __init__(self, root, rev, paths={}):
+ self.root = root
+ self.rev = rev
+ self.paths = paths
+
+ def info(self, pathalias):
+ return self.root, self.rev, self.paths.get(pathalias, None)
+
+ def update(self, rev, paths):
+ self.rev = rev
+ for name, path in paths.items():
+ if not self.paths.has_key(name):
+ self.paths[name] = path
+
+ def write(self, ui, section):
+ ui.write("root = %s\n" % self.root)
+ ui.write("revision = %s\n" % self.rev)
+ ui.write("\n[%s]\n" % (section + ".paths"))
+ for name, path in self.paths.items():
+ ui.write("%s = %s\n" % (name, path))
+
+
+ __slots__ = ('rootmap', 'trees')
+
+ def __init__(self, snapfile=None):
+ self.rootmap = {}
+ self.trees = []
+ if snapfile is not None:
+ cfg = ConfigParser.RawConfigParser()
+ cfg.read([snapfile])
+ index = 0
+ while True:
+ index += 1
+ section = "tree" + str(index)
+ if not cfg.has_section(section):
+ break
+ root = cfg.get(section, 'root')
+ tree = ForestSnapshot.Tree(root, cfg.get(section, 'revision'),
+ dict(cfg.items(section + '.paths')))
+ self.rootmap[root] = tree
+ self.trees.append(tree)
+
+ def __call__(self, ui, toprepo, func, pathalias):
+ """Apply a function to trees matching a snapshot entry.
+
+ Call func(repo, rev, path) for each repo in toprepo and its
+ nested repositories where repo matches a snapshot entry.
+ """
+
+ repo = None
+ for t in self.trees:
+ root, rev, path = t.info(pathalias)
+ ui.write("\n[%s]\n" % root)
+ if path is None:
+ ui.warn(_("no path alias '%s' defined\n") % pathalias)
+ continue
+ if repo is None:
+ repo = toprepo
+ else:
+ try:
+ repo = repository(ui, root)
+ except RepoError:
+ ui.warn(_("no valid repo found\n"))
+ continue
+ if mq_patches_applied(repo.root):
+ ui.warn(_("mq patches applied\n"))
+ continue
+ func(repo, rev, path)
+
+
+ def update(self, ui, repo):
+ """Update a snapshot by scanning a forest.
+
+ If the ForestSnapshot instance to update was initialized from
+ a snapshot file, this regenerate the list of trees with their
+ current revisions but existing path aliases are not touched.
+ """
+
+ rootmap = {}
+ self.trees = []
+ for root in enumerate_repos():
+ if mq_patches_applied(root):
+ raise util.Abort(_("'%s' has mq patches applied") % root)
+ if root != '.':
+ repo = repository(ui, root)
+ rev = mercurial.node.hex(repo.dirstate.parents()[0])
+ paths = dict(repo.ui.configitems('paths'))
+ if self.rootmap.has_key(root):
+ tree = self.rootmap[root]
+ tree.update(rev, paths)
+ else:
+ tree = ForestSnapshot.Tree(root, rev, paths)
+ rootmap[root] = tree
+ self.trees.append(tree)
+ self.rootmap = rootmap
+
+ def write(self, ui):
+ index = 1
+ for t in self.trees:
+ section = 'tree' + str(index)
+ ui.write("\n[%s]\n" % section)
+ t.write(ui, section)
+ index += 1
+
+
+def clone(ui, source, dest, **opts):
+ """Clone a local forest."""
+ source = os.path.normpath(source)
+ dest = os.path.normpath(dest)
+ opts['rev'] = []
+ roots = []
+ for root in enumerate_repos(source):
+ if root == '.':
+ srcpath = source
+ destpath = dest
+ else:
+ subdir = util.localpath(root)
+ srcpath = os.path.join(source, subdir)
+ destpath = os.path.join(dest, subdir)
+ if mq_patches_applied(srcpath):
+ raise util.Abort(_("'%s' has mq patches applied\n") % root)
+ roots.append((root, srcpath, destpath))
+ for root in roots:
+ destpfx = os.path.dirname(root[2])
+ if destpfx and not os.path.exists(destpfx):
+ os.makedirs(destpfx)
+ ui.write("[%s]\n" % root[0])
+ print root[0], root[1], root[2]
+ commands.clone(ui, root[1], root[2], **opts)
+
+
+def pull(ui, toprepo, snapfile, pathalias, **opts):
+ """Pull changes from remote repositories to a local forest.
+
+ Iterate over the entries in the snapshot file and, for each entry
+ matching an actual tree in the forest and with a location
+ associated with 'pathalias', pull changes from this location to
+ the tree.
+
+ Skip entries that do not match or trees for which there is no entry.
+ """
+
+ opts['force'] = None
+ opts['rev'] = []
+
+ def doit(repo, rev, path):
+ commands.pull(repo.ui, repo, path, **opts)
+
+ snapshot = ForestSnapshot(snapfile)
+ snapshot(ui, toprepo, doit, pathalias)
+
+
+def push(ui, toprepo, snapfile, pathalias, **opts):
+ """Push changes in a local forest to remote destinations.
+
+ Iterate over the entries in the snapshot file and, for each entry
+ matching an actual tree in the forest and with a location
+ associated with 'pathalias', push changes from this tree to the
+ location.
+
+ Skip entries that do not match or trees for which there is no entry.
+ """
+
+ opts['force'] = None
+ opts['rev'] = []
+
+ def doit(repo, rev, path):
+ commands.push(repo.ui, repo, path, **opts)
+
+ snapshot = ForestSnapshot(snapfile)
+ snapshot(ui, toprepo, doit, pathalias)
+
+
+def seed(ui, repo, snapshot, pathalias, **opts):
+ """Populate a forest according to a snapshot file."""
+
+ cfg = ConfigParser.RawConfigParser()
+ cfg.read(snapshot)
+ index = 1
+ while True:
+ index += 1
+ section = 'tree' + str(index)
+ if not cfg.has_section(section):
+ break
+ root = cfg.get(section, 'root')
+ psect = section + '.paths'
+ if not cfg.has_option(psect, pathalias):
+ ui.warn(_("no path alias '%s' defined for tree '%s'\n") %
+ (pathalias, dest))
+ continue
+ source = cfg.get(psect, pathalias)
+ ui.write("\n[%s]\n" % root)
+ dest = util.localpath(root)
+ if os.path.exists(dest):
+ ui.warn(_("destination '%s' already exists, skipping\n") % dest)
+ continue
+ destpfx = os.path.dirname(dest)
+ if destpfx and not os.path.exists(destpfx):
+ os.makedirs(destpfx)
+ # 'clone -r rev' not implemented for remote repos (<= 0.9), use
+ # 'update' if necessary
+ opts['rev'] = []
+ commands.clone(ui, source, dest, **opts)
+ if not opts['tip']:
+ rev = cfg.get(section, 'revision')
+ if rev and rev != mercurial.node.nullid:
+ repo = repository(ui, dest)
+ commands.update(repo.ui, repo, node=rev)
+
+
+def snapshot(ui, repo, snapfile=None):
+ """Generate or update a forest snapshot and display it."""
+
+ snapshot = ForestSnapshot(snapfile)
+ snapshot.update(ui, repo)
+ snapshot.write(ui)
+
+
+def status(ui, repo, *pats, **opts):
+ """Display the status of a forest of working directories."""
+
+ for root in enumerate_repos():
+ mqflag = ""
+ if mq_patches_applied(repo.root):
+ mqflag = " *mq*"
+ ui.write("\n[%s]%s\n" % (root, mqflag))
+ repo = repository(ui, root)
+ commands.status(repo.ui, repo, *pats, **opts)
+
+
+def trees(ui, repo):
+ """List the roots of the repositories."""
+
+ for root in enumerate_repos():
+ ui.write(root + '\n')
+
+
+cmdtable = {
+ "forest-clone" :
+ (clone,
+ cmd_options('clone', remove=('r',)),
+ _('hg forest-clone [OPTIONS] SOURCE DESTINATION')),
+ "forest-pull" :
+ (pull,
+ cmd_options('pull', remove=('f', 'r')),
+ _('hg forest-pull [OPTIONS] SNAPSHOT-FILE PATH-ALIAS')),
+ "forest-push" :
+ (push,
+ cmd_options('push', remove=('f', 'r')),
+ _('hg forest-push [OPTIONS] SNAPSHOT-FILE PATH-ALIAS')),
+ "forest-seed" :
+ (seed,
+ [('', 'tip', None,
+ _("use tip instead of revisions stored in the snapshot file"))] +
+ cmd_options('clone', remove=('r',)),
+ _('hg forest-seed [OPTIONS] SNAPSHOT-FILE PATH-ALIAS')),
+ "forest-snapshot" :
+ (snapshot, [],
+ 'hg forest-snapshot [SNAPSHOT-FILE]'),
+ "forest-status" :
+ (status,
+ cmd_options('status'),
+ _('hg forest-status [OPTIONS]')),
+ "forest-trees" :
+ (trees, [],
+ 'hg forest-trees'),
+}
More information about the CIG-COMMITS
mailing list