changeset 0:94469fb5f177

initial revision Subtree information is stored in the file .hg/trees, with a format of one subtree per line.
author jcoomes
date Wed, 13 Oct 2010 23:41:12 -0700
parents
children ecf1aa057cc2
files makefile tests/hgrc trees.py
diffstat 3 files changed, 727 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/makefile	Wed Oct 13 23:41:12 2010 -0700
@@ -0,0 +1,242 @@
+# 
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+# 
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+# 
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+# 
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+# 
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+# 
+
+# Use $hg from the environment, if set.
+HG		:= $(word 1,${hg} hg)
+
+# Installation.
+PREFIX		:= ${HG_INSTALL_BASE}/hg
+SRC_D		:= .
+TREES_PY	:= ${SRC_D}/trees.py
+SRC_F		:= trees.py
+GET_PY_VER	:= import sys; print ".".join(sys.version.split(".")[0:2])
+PY_VER		:= $(shell python -c '${GET_PY_VER}')
+DST_D		= ${PREFIX}/lib/python${PY_VER}/site-packages/hgext
+DST_F		= $(addprefix ${DST_D}/,${SRC_F})
+
+# Testing.
+TEST_RESULTS	:= test-results
+TR		:= ${TEST_RESULTS}
+TEST_CMDS1	= list paths st up
+TEST_CMDS2	= in out pull push
+TESTS_1		:= $(foreach r,r1 r2 r3 r4,$(addprefix ${r}.,${TEST_CMDS1}))
+TESTS_2		:= $(foreach r,r1 r2,$(addprefix ${r}.,${TEST_CMDS2}))
+TESTS		= $(TESTS_1) $(TESTS_2) r4.config hs.paths
+
+HGRC		:= ${TR}/hgrc
+RUN_HG		:= HGRCPATH=${HGRC} ${HG}
+
+QUIET		:= @
+
+.PHONY:  all install test version ${TESTS} r1.cset r2.cset FORCE
+
+all:
+	${QUIET} echo '${MAKE} install [PREFIX=...]'
+	${QUIET} echo '${MAKE} test [ARGS=...]'
+	${QUIET} echo '${MAKE} ${TESTS_1} [ARGS=...]'
+	${QUIET} echo '${MAKE} ${TESTS_2} [ARGS=...]'
+	${QUIET} echo '${MAKE} r4.config hs.paths [ARGS=...]'
+
+clean:
+	rm -fr "${TR}" "${SRC_D}"/*.pyc
+
+install:  ${DST_F}
+
+# File still seen as out of date, even with cp -p.  Grrr.
+${DST_D}/%: ${SRC_D}/%
+	cp -p '$^' '$@' && touch '$@'
+
+test:  ${TESTS}
+
+version:
+	${RUN_HG} version
+
+${TR}:
+	mkdir -p ${TR}
+
+${HGRC}:  ${SRC_D}/tests/hgrc makefile
+	${QUIET} [ -d '${TR}' ] || mkdir -p '${TR}'
+	${QUIET} cp '$<' '$@'
+	${QUIET} \
+	{ \
+	echo; \
+	echo '[extensions]'; \
+	echo 'trees = ${TREES_PY}'; \
+	echo; \
+	echo '[trees]'; \
+	echo 's1s2 = s1 s2'; \
+	echo 'hs-closed = src/closed test/closed'; \
+	} >> '$@'
+
+${TR}/r1:  ${HGRC}
+	rm -fr '$@'
+	${QUIET} echo creating '$@' ...
+	${QUIET} \
+	all=''; \
+	for r in '$@' \
+		'$@/s1' \
+		'$@/s1/s1.1 with spaces' \
+		'$@/s2' \
+		'$@/s2/s2.1' \
+		'$@/s2/s2.2' \
+		'$@/s2/s2.2/s2.2.1'; \
+	do \
+		${RUN_HG} init "$$r" && \
+		echo $$r > "$$r/x" && \
+		${RUN_HG} -R "$$r" ci -qAm "$$r"; \
+		if [ -n "$$all" ]; \
+		then \
+			all="$$all\n$${r:-.}"; \
+		else \
+			all="$${r:-.}"; \
+		fi; \
+	done; \
+	root=`${RUN_HG} -R $@ root`; \
+	echo "$$all" | sed 's!$@!'"$$root"'!' > '$@.list.ref'
+	${QUIET} { \
+		echo s1; echo s2; \
+	} >> '$@'/.hg/trees
+	${QUIET} { \
+		echo 's1.1 with spaces'; \
+	} >> '$@'/s1/.hg/trees
+	${QUIET} { \
+		echo s2.1; echo s2.2; echo s2.2/s2.2.1; \
+	} >> '$@'/s2/.hg/trees
+
+${TR}/r2:  ${TR}/r1 ${TREES_PY}
+	rm -fr '$@'
+	${RUN_HG} tclone ${ARGS} $< '$@'
+	${QUIET} sed 's!$<!$@!' ${<}.list.ref > ${@}.list.ref
+	${QUIET} cp -p ${<}.list.ref ${@}.paths.ref
+	@ echo
+
+${TR}/r3:  ${TR}/r1 ${TR}/r2 ${TREES_PY}
+	rm -fr '$@'
+	${RUN_HG} tclone $< '$@' s1 file:${TR}/r2 s2
+	${QUIET} sed 's!$<!$@!' ${<}.list.ref > ${@}.list.ref
+	${QUIET} sed 's!$</s2!${TR}/r2/s2!' ${<}.list.ref > ${@}.paths.ref
+	@ echo
+
+${TR}/r4:  ${TR}/r1 ${TR}/r2 ${TREES_PY}
+	rm -fr '$@'
+	${RUN_HG} tclone $< '$@' s1 file:${TR}/r2 s2
+	${QUIET} sed 's!$<!$@!' ${<}.list.ref > ${@}.list.ref
+	@ echo
+
+%.up:	rev	= tip
+r1.%:	rlocal	= '${TR}/r1'
+r1.%:	rother	= '${TR}/r2'
+r2.%:	rlocal	= '${TR}/r2'
+r2.%:	rother	=
+r3.%:	rlocal	= '${TR}/r3'
+r4.%:	rlocal	= '${TR}/r4'
+
+rx_treecmd	= t$(subst $(basename $@).,,$@)
+rx_redirect	= 2>&1 | tee ${TR}/${@}.raw
+rx_pathsfilt	= sed -n 's/default[ 	]*=[ 	]*//p' ${TR}/${@}.raw > ${TR}/${@}.out
+rx_diff		= if [ -f ${TR}/${@}.ref ]; then diff -u ${TR}/${@}.ref ${TR}/${@}.out; fi
+
+r1.cset: ${TR}/r1 FORCE
+	${QUIET} for r in s2 s2/s2.2; \
+	do \
+		echo $$r >> ${rlocal}/$$r/w; \
+		${RUN_HG} -R ${rlocal}/$$r ci -qAm "$@ $$r w"; \
+		echo $$r >> ${rlocal}/$$r/x; \
+		${RUN_HG} -R ${rlocal}/$$r ci -qAm "$@ $$r x"; \
+	done
+
+r2.cset: ${TR}/r2 FORCE
+	${QUIET} for r in s1 s2/s2.1 s2/s2.2/s2.2.1; \
+	do \
+		echo $$r >> ${rlocal}/$$r/y; \
+		${RUN_HG} -R ${rlocal}/$$r ci -qAm "$@ $$r y"; \
+		echo $$r >> ${rlocal}/$$r/z; \
+		${RUN_HG} -R ${rlocal}/$$r ci -qAm "$@ $$r z"; \
+	done
+
+${TESTS_1}:  ${TR}/r3 ${TR}/r4
+	${RUN_HG} -R ${rlocal} ${rx_treecmd} ${ARGS} ${rev} ${rx_redirect}
+	${QUIET} \
+	case "$@" in \
+	*.paths) ${rx_pathsfilt};; \
+	*) mv ${TR}/${@}.raw ${TR}/${@}.out;; \
+	esac
+	${QUIET} ${rx_diff}
+	@ echo
+
+r4.config: ${TR}/r4
+	rm -fr ${rlocal}/s3
+	${RUN_HG} init ${rlocal}/s3
+
+	cat ${rlocal}/.hg/trees
+	${RUN_HG} tconfig -R ${rlocal}
+
+	${RUN_HG} tconfig -R ${rlocal} -a s3
+	if ${RUN_HG} tconfig -R ${rlocal} -a s3; then exit 1; fi
+	cat ${rlocal}/.hg/trees
+	${RUN_HG} tconfig -R ${rlocal}
+
+	${RUN_HG} tconfig -R ${rlocal} -s s1 s2
+	cat ${rlocal}/.hg/trees
+	${RUN_HG} tconfig -R ${rlocal}
+
+	${RUN_HG} tconfig -R ${rlocal} -s
+	cat ${rlocal}/.hg/trees
+	${RUN_HG} tconfig -R ${rlocal}
+
+	${RUN_HG} tconfig -R ${rlocal} -s s1 s2 s3
+	cat ${rlocal}/.hg/trees
+	${RUN_HG} tconfig -R ${rlocal}
+
+	${RUN_HG} tconfig -R ${rlocal} -d s1
+	cat ${rlocal}/.hg/trees
+	${RUN_HG} tconfig -R ${rlocal}
+
+	${RUN_HG} tconfig -R ${rlocal} -sw
+	cat ${rlocal}/.hg/trees
+	${RUN_HG} tconfig -R ${rlocal}
+
+r1.in r1.pull r2.out r2.push:  r2.cset
+	${RUN_HG} -R ${rlocal} ${rx_treecmd} ${ARGS} ${rother}
+r1.out r1.push r2.in r2.pull:  r1.cset
+	${RUN_HG} -R ${rlocal} ${rx_treecmd} ${ARGS} ${rother}
+
+# combine repos from different trees (http)
+hs_url_o	= http://hg.openjdk.java.net/jdk7/hotspot
+hs_url_c	= http://closedjdk.sfbay.sun.com/jdk7/hotspot
+${TR}/hs:  ${HGRC} ${TREES_PY}
+	rm -fr '$@'
+	${RUN_HG} tclone '${hs_url_o}' '$@' pubs '${hs_url_c}-gc' hs-closed
+	${QUIET} \
+	for p in '${hs_url_o}' '${hs_url_o}'/pubs \
+		'${hs_url_c}-gc/src/closed' '${hs_url_c}-gc/test/closed'; \
+	do \
+		echo "$$p"; \
+	done > $@.paths.ref
+
+	@ echo
+
+hs.paths:  ${TR}/hs
+	${RUN_HG} -R '$<' tpaths ${ARGS} ${rx_redirect}
+	${QUIET} ${rx_pathsfilt}
+	${QUIET} ${rx_diff}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/hgrc	Wed Oct 13 23:41:12 2010 -0700
@@ -0,0 +1,9 @@
+[defaults]
+backout = -d "0 0"
+commit = -d "0 0"
+tag = -d "0 0"
+ttag = -d "0 0"
+
+[ui]
+slash = True
+username = anontester
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trees.py	Wed Oct 13 23:41:12 2010 -0700
@@ -0,0 +1,476 @@
+#
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+'''manage loosely-coupled nested repositories
+'''
+
+import __builtin__
+import inspect
+import os
+import re
+import subprocess
+
+from mercurial import cmdutil
+from mercurial import commands
+from mercurial import hg
+from mercurial import pushkey
+from mercurial import ui
+from mercurial import util
+from mercurial.i18n import _
+
+def _checklocal(repo):
+    if not repo.local():
+        raise util.Abort(_('repository is not local'))
+
+def _expandsubtrees(ui, subtrees):
+    '''Expand subtree 'aliases.'
+
+    Each string in subtrees that has a like-named config entry in the [trees]
+    section is replaced by the right hand side of the config entry.'''
+    l = []
+    for subtree in subtrees:
+        if '/' in subtree:
+            l += [subtree]
+        else:
+            cfglist = ui.configlist('trees', subtree)
+            if cfglist:
+                l += _expandsubtrees(ui, cfglist)
+            else:
+                l += [subtree]
+    return l
+
+def _subtreelist(ui, repo, opts):
+    l = opts.get('subtrees')
+    if l:
+        del opts['subtrees']
+        return _expandsubtrees(ui, l)
+    l = []
+    try:
+        for line in repo.opener('trees'):
+            l += [line.rstrip('\r\n')]
+    except:
+        pass
+    return l
+
+def _subtreegen(ui, repo, opts):
+    '''yields (repo, subtree) tuples'''
+    l =  _subtreelist(ui, repo, opts)
+    if l:
+        for subtree in l:
+            # Look for file://..., http://..., ssh://..., etc.
+            if subtree.split(':', 2)[0] in hg.schemes:
+                repo = hg.repository(ui, subtree)
+            else:
+                yield repo, subtree
+        return
+    if repo.capable('pushkey'):
+        s = repo.listkeys('trees')
+        i = 0
+        n = len(s)
+        while i < n:
+            subtree = s[("%d" % i)]
+            # Look for file://..., http://..., ssh://..., etc.
+            if subtree.split(':', 2)[0] in hg.schemes:
+                repo = hg.repository(ui, subtree)
+            else:
+                yield repo, subtree
+            i += 1
+
+def _makeparentdir(path):
+    if path:
+        pdir = os.path.split(path.rstrip(os.sep + '/'))[0]
+        if pdir and not os.path.exists(pdir):
+            os.makedirs(pdir)
+
+def _shortpaths(root, subtrees):
+    l = []
+    if subtrees:
+        n = len(root) + 1
+        if subtrees[0] == root:
+            l = ['.']
+        else:
+            l = [subtrees[0][n:]]
+        for subtree in subtrees[1:]:
+            l += [subtree[n:]]
+    return l
+
+def _subtreejoin(repo, subtree):
+    '''return a string (url or path) referring to subtree within repo'''
+    if repo.local():
+        return repo.wjoin(subtree)
+    return repo.url().rstrip('/') + '/' + subtree
+
+def _walk(ui, repo, opts):
+    l = []
+    for dirpath, subdirs, files in os.walk(repo.root, True):
+        if '.hg' in subdirs:
+            subdirs.remove('.hg')
+            l += [dirpath]
+    return sorted(l)
+
+def _writeconfig(repo, subtrees):
+    f = open(repo.join('trees'), 'w')
+    try:
+        if subtrees:
+            f.write('\n'.join(subtrees) + '\n')
+    finally:
+        f.close()
+    return 0
+
+# ---------------- commands and associated recursion helpers -------------------
+
+def clone(ui, source, dest=None, *subtreeargs, **opts):
+    '''copy one or more existing repositories to create a tree'''
+    if subtreeargs:
+        s = __builtin__.list(subtreeargs)
+        s.extend(opts.get('subtrees')) # Note:  extend does not return a value
+        opts['subtrees'] = s
+
+    ui.status('cloning %s\n' % source)
+    _makeparentdir(dest)
+    # Copied from mercurial/hg.py; need the returned dest repo.
+    r = hg.clone(hg.remoteui(ui, opts), source, dest,
+                 pull=opts.get('pull'),
+                 stream=opts.get('uncompressed'),
+                 rev=opts.get('rev'),
+                 update=opts.get('updaterev') or not opts.get('noupdate'),
+                 branch=opts.get('branch'))
+    src = r[0]
+    dst = r[1]
+    ui.status(_('created %s\n') % dst.root)
+
+    subtrees = []
+    for src, subtree in _subtreegen(src.ui, src, opts):
+        ui.status('\n')
+        clone(ui, _subtreejoin(src, subtree), dst.wjoin(subtree), **opts)
+        subtrees.append(subtree)
+    _writeconfig(dst, subtrees)
+    return 0
+
+def _command(ui, repo, argv, stop, opts):
+    ui.status('[%s]:\n' % repo.root)
+    ui.flush()
+    # Mercurial bug?  util.system() drops elements of argv after the first.
+    # rc = util.system(argv, cwd=repo.root)
+    rc = subprocess.call(argv, cwd=repo.root)
+    if rc and stop:
+        return rc
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        ui.flush()
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        rc += _command(lr.ui, lr, argv, stop, opts)
+        if rc and stop:
+            return rc
+    return rc
+
+def command(ui, repo, cmd, *args, **opts):
+    '''run a command in each repo in the tree.
+
+    Change directory to the root of each repo and run the command.
+
+    Note that mercurial normally parses all arguments that start with a dash,
+    including those that follow the command name, which usually results in an
+    error.  Prevent this by using '--' before the command or arguments, e.g.:
+
+    hg tcommand -- ls -l
+    '''
+
+    _checklocal(repo)
+    l = __builtin__.list((cmd,) + args)
+    return _command(ui, repo, l, opts.get('stop'), opts)
+
+def config(ui, repo, *subtrees, **opts):
+    '''list or change the subtrees configuration
+
+    One of four operations can be selected:  --list, --add, --del, or --set.
+    If no operation is specified, --list is assumed.
+
+    If the --walk option is used with --set, the filesystem rooted at
+    REPO is scanned and the subtree configuration set to the discovered repos.
+    '''
+
+    _checklocal(repo)
+
+    opadd = opts.get('add')
+    opdel = opts.get('del')
+    oplst = opts.get('list')
+    opset = opts.get('set')
+    cnt = opadd + opdel + oplst + opset
+    if cnt > 1:
+        raise util.Abort(_('at most one of --add, --del, --list or --set is ' +
+                           'allowed'))
+    if cnt == 0 or oplst:
+        l = _subtreelist(ui, repo, opts)
+        for subtree in l:
+            ui.write(subtree + '\n')
+        return 0
+    if opadd and subtrees:
+        l = _subtreelist(ui, repo, opts)
+        for subtree in subtrees:
+            if subtree in l:
+                raise util.Abort(_('subtree %s already configured' % subtree))
+            l += [subtree]
+        return _writeconfig(repo, l)
+    if opdel:
+        all = opts.get('all')
+        if all + bool(subtrees) != 1:
+            raise util.Abort(_('use either --all or subtrees (but not both)'))
+        if all:
+            return _writeconfig(repo, [])
+        l = _subtreelist(ui, repo, opts)
+        for subtree in subtrees:
+            if not subtree in l:
+                raise util.Abort(_('no subtree %s' % subtree))
+            l.remove(subtree)
+        return _writeconfig(repo, l)
+    if opset:
+        walk = opts.get('walk')
+        if walk + bool(subtrees) != 1:
+            raise util.Abort(_('use either --walk or subtrees (but not both)'))
+        l = subtrees
+        if walk:
+            l = _shortpaths(repo.root, _walk(ui, repo, {}))[1:]
+        return _writeconfig(repo, l)
+
+def _incoming(ui, repo, remote, adjust, opts):
+    ui.status('[%s]:\n' % repo.root)
+    commands.incoming(ui, repo, remote, **opts)
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        remote2 = adjust and os.path.join(remote, subtree) or remote
+        _incoming(lr.ui, lr, remote2, adjust, opts)
+    return 0
+
+def incoming(ui, repo, remote="default", **opts):
+    '''show new changesets found in source '''
+    _checklocal(repo)
+    adjust = remote and not ui.config('paths', remote)
+    return _incoming(ui, repo, remote, adjust, opts)
+
+def _list(ui, repo, opts):
+    l = [repo.root]
+    for subtree in _subtreelist(ui, repo, opts):
+        dir = repo.wjoin(subtree)
+        if os.path.exists(dir):
+            lr = hg.repository(ui, dir)
+            l += _list(lr.ui, lr, opts)
+        else:
+            ui.warn('repo %s is missing subtree %s\n' % (repo.root, subtree))
+    return l
+
+def list(ui, repo, **opts):
+    '''list the repo and configured subtrees, recursively
+
+    The initial list of subtrees is obtained from the command line (if present)
+    or from the repo's subtrees configuration.
+
+    If the --walk option is specified, search the filesystem for subtrees
+    instead of using the command line or configuration item.
+
+    If the --short option is specified, "short" paths are listed (relative to
+    the top-level repo).'''
+
+    _checklocal(repo)
+    if opts.get('walk'):
+        l = _walk(ui, repo, opts)
+    else:
+        l = _list(ui, repo, opts)
+    if opts.get('short'):
+        l = _shortpaths(repo.root, l)
+    for subtree in l:
+        ui.write(subtree + '\n')
+    return 0
+
+def _outgoing(ui, repo, remote, adjust, opts):
+    ui.status('[%s]:\n' % repo.root)
+    commands.outgoing(ui, repo, remote, **opts)
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        remote2 = adjust and os.path.join(remote, subtree) or remote
+        _outgoing(lr.ui, lr, remote2, adjust, opts)
+    return 0
+
+def outgoing(ui, repo, remote=None, **opts):
+    '''show changesets not found in the destination'''
+    _checklocal(repo)
+    adjust = remote and not ui.config('paths', remote)
+    return _outgoing(ui, repo, remote, adjust, opts)
+
+def paths(ui, repo, search=None, **opts):
+    '''show aliases for remote repositories'''
+    _checklocal(repo)
+    ui.status('[%s]:\n' % repo.root)
+    commands.paths(ui, repo, search)
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        paths(lr.ui, lr, search, **opts)
+    return 0
+
+def _pull(ui, repo, remote, adjust, opts):
+    ui.status('[%s]:\n' % repo.root)
+    commands.pull(ui, repo, remote, **opts)
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        remote2 = adjust and os.path.join(remote, subtree) or remote
+        _pull(lr.ui, lr, remote2, adjust, opts)
+    return 0
+
+def pull(ui, repo, remote="default", **opts):
+    '''pull changes from the specified source'''
+    _checklocal(repo)
+    adjust = remote and not ui.config('paths', remote)
+    return _pull(ui, repo, remote, adjust, opts)
+
+def _push(ui, repo, remote, adjust, opts):
+    ui.status('[%s]:\n' % repo.root)
+    commands.push(ui, repo, remote, **opts)
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        remote2 = adjust and os.path.join(remote, subtree) or remote
+        _push(lr.ui, lr, remote2, adjust, opts)
+    return 0
+
+def push(ui, repo, remote=None, **opts):
+    '''push changes to the specified destination'''
+    _checklocal(repo)
+    adjust = remote and not ui.config('paths', remote)
+    return _push(ui, repo, remote, adjust, opts)
+
+def status(ui, repo, *pats, **opts):
+    '''show changed files in the working directory'''
+    _checklocal(repo)
+    ui.status('[%s]:\n' % repo.root)
+    commands.status(ui, repo, *pats, **opts)
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        status(lr.ui, lr, *pats, **opts)
+    return 0
+
+def tag(ui, repo, name1, *names, **opts):
+    '''add one or more tags for the current or given revision'''
+    _checklocal(repo)
+    ui.status('[%s]:\n' % repo.root)
+    commands.tag(ui, repo, name1, *names, **opts)
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        tag(lr.ui, lr, name1, *names, **opts)
+    return 0
+
+def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
+           **opts):
+    '''update working directory (or switch revisions)'''
+    _checklocal(repo)
+    ui.status('[%s]:\n' % repo.root)
+    commands.update(ui, repo, node, rev, clean, date, check)
+    for subtree in _subtreelist(ui, repo, opts):
+        ui.status('\n')
+        lr = hg.repository(ui, repo.wjoin(subtree))
+        update(lr.ui, lr, node, rev, clean, date, check, **opts)
+    return 0
+
+def version(ui, **opts):
+    '''show version information'''
+    ui.status('trees extension (version 0.5)\n')
+
+def debugkeys(ui, src, **opts):
+    repo = hg.repository(ui, src)
+    print(repo.listkeys('trees'))
+
+# ----------------------------- mercurial linkage ------------------------------
+
+subtreesopt = [('', 'subtrees', [],
+                _('path to subtree'),
+                _('SUBTREE'))]
+
+walkopt = [('w', 'walk', False,
+            _('walk the filesystem to discover subtrees'))]
+
+commandopts = [('', 'stop', False,
+                _('stop if command returns non-zero'))
+              ] + subtreesopt
+listopts = [('s', 'short', False,
+             _('list short paths (relative to repo root)'))
+           ] + walkopt + subtreesopt
+configopts = [('a', 'add', False,
+               _('add the specified SUBTREEs to config')),
+              ('',  'all', False,
+               _('with --del, delete all subtrees from config')),
+              ('d', 'del', False,
+               _('delete the specified SUBTREEs from config')),
+              ('l', 'list', False,
+               _('list the configured subtrees')),
+              ('s', 'set', False, _('set the subtree config to SUBTREEs'))
+             ] + walkopt
+
+def _newcte(origcmd, newfunc, extraopts = [], synopsis = None):
+    '''generate a cmdtable entry based on that for origcmd'''
+    cte = cmdutil.findcmd(origcmd, commands.table)[1]
+    if (len(cte) > 2):
+        return (newfunc, cte[1] + extraopts, synopsis or cte[2])
+    return (newfunc, cte[1] + extraopts, synopsis)
+
+# Commands tagged with '^' are listed by 'hg help'.
+cmdtable = {
+    '^tclone': _newcte('clone', clone, subtreesopt,
+                       _('[OPTION]... SOURCE [DEST [SUBTREE]...]')),
+    'tcommand': (command, commandopts, _('command [arg] ...')),
+    'tconfig': (config, configopts, _('[OPTION]... [SUBTREE]...')),
+    'tincoming': _newcte('incoming', incoming, subtreesopt),
+    'toutgoing': _newcte('outgoing', outgoing, subtreesopt),
+    'tlist': (list, listopts, _('[OPTION]...')),
+    'tpaths': _newcte('paths', paths, subtreesopt),
+    '^tpull': _newcte('pull', pull, subtreesopt),
+    '^tpush': _newcte('push', push, subtreesopt),
+    '^tstatus': _newcte('status', status, subtreesopt),
+    '^tupdate': _newcte('update', update, subtreesopt),
+    'ttag': _newcte('tag', tag, subtreesopt),
+    'tversion': (version, [], ''),
+    'tdebugkeys': (debugkeys, [], '')
+}
+
+commands.norepo += ' tclone tversion tdebugkeys'
+
+def _treeslistkeys(repo):
+    # trees are ordered, so the keys are the non-negative integers.
+    s = {}
+    i = 0
+    try:
+        for line in repo.opener('trees'):
+            s[("%d" % i)] = line.rstrip('\n\r')
+            i += 1
+        return s
+    except:
+        return {}
+
+def reposetup(ui, repo):
+    if r.capable('pushkey'):
+        # Pushing keys is disabled; unclear whether/how it should work.
+        pushkey.register('trees', lambda *x: False, _treeslistkeys)