diff options
author | Andrew McDermott <andrew.mcdermott@linaro.org> | 2014-03-19 10:56:29 +0000 |
---|---|---|
committer | Andrew McDermott <andrew.mcdermott@linaro.org> | 2014-03-19 12:04:43 +0000 |
commit | 290b311f403d55e881cdeb6f6613e3f4a0161bbd (patch) | |
tree | 5efe856e3582b83ae2a7d257a6bc7553efd45686 | |
parent | ff9b689348e4a770132fe34ea93739d996bd44dd (diff) |
Add script to clone aarch64-port tree using Mercurial
Signed-off-by: Andrew McDermott <andrew.mcdermott@linaro.org>
-rw-r--r-- | .gitignore | 1 | ||||
-rwxr-xr-x | openjdk-clone-tree | 63 | ||||
-rw-r--r-- | trees.py | 994 |
3 files changed, 1058 insertions, 0 deletions
@@ -2,3 +2,4 @@ net/ jdk8/ openjdk8-aarch64-port-snapshot/ openjdk8-jdk8-snapshot/ +out/ diff --git a/openjdk-clone-tree b/openjdk-clone-tree new file mode 100755 index 0000000..7966902 --- /dev/null +++ b/openjdk-clone-tree @@ -0,0 +1,63 @@ +#!/bin/bash + +# Copyright (C) 2014, Linaro Limited. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Author: Andrew McDermott <andrew.mcdermott@linaro.org> + +set -eu + +progname=$(basename $0) +SCRIPTPATH=$(cd $(dirname $0); pwd -P) + +: ${DOWNLOAD_DIR:=/tmp/${progname}-download.$$} + +tdir=$(mktemp -d /tmp/${progname}-hgrc-XXXXXX.$$) +echo "[extensions]" > $tdir/.hgrc +echo "trees=${SCRIPTPATH}/trees.py" >> $tdir/.hgrc + +trap "rm -rf $tdir" EXIT + +export HGRCPATH=${tdir}/.hgrc + +: ${URL:=http://hg.openjdk.java.net/aarch64-port/jdk8} + +if [ ! -d jdk8 ]; then + hg tclone ${URL} +else + hg --cwd jdk8 tpull + hg --cwd jdk8 tupdate +fi + +pushd jdk8 >/dev/null +commit_id=$(hg identify | awk '{print $1}') +echo "jdk8-${commit_id}" > BOM +for i in corba jaxp jaxws langtools jdk hotspot nashorn; do + pushd $i >/dev/null + commit_id=$(hg identify | awk '{print $1}') + dir=$(basename $PWD) + echo "${dir}-${commit_id}" >> ../BOM + popd >/dev/null +done +popd >/dev/null + +rm -rf out +mkdir -p out + +[ -e jdk8/BOM ] && cat jdk8/BOM +tar -acf out/openjdk8-aarch64-port-aarch64-port.tar.bz2 --exclude=.hg --transform='s/^jdk8/openjdk8-aarch64-port/' jdk8 +ls -l out/*.bz2 +md5sum out/*.bz2 diff --git a/trees.py b/trees.py new file mode 100644 index 0000000..9cbc5c0 --- /dev/null +++ b/trees.py @@ -0,0 +1,994 @@ +# +# Copyright (c) 2010, 2014, 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 + +A 'tree' is simply a repository that may contain other repositories (or other +trees) nested within its working directory. This extension provides commands +that operate on an entire tree, or on selected trees within it. + +Each tree stores a list of the repositories contained within it in a +non-versioned file, .hg/trees. The file lists the path to each contained tree, +relative to the root, one per line. The file is created when a tree is cloned, +and can be modified using the tconfig command. It is not updated when pushing +or pulling changesets (tpush, tpull). + +The extension is usable on the client even if the hg server does not have the +trees extension enabled; it simply requires a bit more typing when cloning +repos. Each repo in the tree maintains its own path information in .hg/hgrc, so +that repositories from different locations can be combined into a single tree. + +The following example creates a tree called 'myproj' that includes four nested +repositories ('src', 'docs', 'images' and 'styles') with the last two coming +from a different server. If the hg servers have the trees extension enabled:: + + $ hg tclone http://abc/proj myproj + $ hg tclone --skiproot http://xyz/pub myproj + +If the hg servers do not have the trees extension enabled, then simply append +the desired contained repos (subtrees) to the command line:: + + $ hg tclone http://abc/proj myproj src docs + $ hg tclone --skiproot http://xyz/pub myproj images styles + +The above is shorthand for five clone operations (three from the first command, +and two more from the second):: + + $ hg clone http://abc/proj myproj + $ hg clone http://abc/proj/src myproj/src + $ hg clone http://abc/proj/docs myproj/docs + $ hg clone http://xyz/pub/images myproj/images + $ hg clone http://xyz/pub/styles myproj/styles + +It also writes the tree configuration to myproj/.hg/trees. Note that the same +tree can also be created with a single tclone:: + + $ hg tclone http://abc/proj myproj src docs http://xyz/pub images styles + +More examples:: + +Show the working directory status for each repo in the tree:: + + $ hg tstatus + +Pull upstream changes into each repo in the tree and update the working dir:: + + $ hg tpull -u + +Push local changes from each repo in the tree:: + + $ hg tpush + +List the tree configuration recursively:: + + $ hg tlist + /path/to/myproj + /path/to/myproj/src + /path/to/myproj/docs + /path/to/myproj/images + /path/to/myproj/styles + +You can create abbreviations for tree lists by adding a [trees] section to your +hgrc file, e.g.:: + + [trees] + jdk-lt = jdk langtools + ojdk-all = corba hotspot jaxp jaxws jdk-lt + +which could be used like this: + + $ hg tclone http://hg.openjdk.java.net/jdk7/jdk7 myjdk7 ojdk-all + +to create the myjdk7 tree which contains corba, hotspot, jaxp, jaxws, jdk and +langtools repos. + +Show the working directory status, but only for the root repo plus the jdk and +langtools repos:: + + $ hg tstatus --subtrees jdk-lt +""" + +import __builtin__ +import exceptions +import inspect +import os +import re +import subprocess + +from mercurial import cmdutil +from mercurial import commands +from mercurial import extensions +from mercurial import hg +from mercurial import localrepo +from mercurial import pushkey +from mercurial import ui +from mercurial import util +from mercurial.i18n import _ + +testedwith = ''' +1.1 1.1.2 1.2 1.2.1 1.3 1.3.1 1.4 1.4.3 1.5 1.5.4 +1.6 1.6.4 1.7 1.7.5 1.8 1.8.4 1.9 1.9.3 +2.0-rc 2.0 2.0.2 2.1-rc 2.1 2.1.2 2.2-rc 2.2 2.2.3 +2.3-rc 2.3 2.3.2 2.4-rc 2.4 2.4.2 2.5-rc 2.5 2.5.4 +2.6-rc 2.6 2.6.3 2.7-rc 2.7 2.7.2 2.8-rc 2.8 2.8.2 +2.9-rc 2.9 +''' + +def _checklocal(repo): + if not isinstance(repo, localrepo.localrepository): + 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 _nsnormalize(s): + if s == 'trees' or s.startswith('trees.'): + return s + return 'trees.' + s + +def _ns(ui, opts): + return _nsnormalize(opts.get('tns') or + ui.config('trees', 'namespace', 'trees')) + +_splitui = None + +def _splitsubtrees(l): + global _splitui + res = [] + for s in l: + if "'" in s or '"' in s: + # Use ui.configlist() for quoted strings; requires hg 1.6 or later. + if not _splitui: + _splitui = ui.ui() + _splitui.setconfig('x', 'x', s) + res += _splitui.configlist('x', 'x') + else: + res += s.split() + return res + +def _subtreelist(ui, repo, opts): + l = opts.get('subtrees') + if l: + del opts['subtrees'] + cansplit = ui.configbool('trees', 'splitargs', True) + return _expandsubtrees(ui, cansplit and _splitsubtrees(l) or l) + l = [] + try: + keys = repo.listkeys(_ns(ui, opts)) + for i in xrange(0, len(keys)): + l.append(keys[str(i)]) + except: + pass + return l + +def hg_repo(ui, url, opts): + parts = url.split(':', 2) + if len(parts) == 1 or parts[0] == 'file': + return hg.repository(ui, url) + return hg.peer(ui, opts, url) + +def _subtreegen_listkeys(ui, repo, opts, namespace): + keys = repo.listkeys(namespace) + for i in range(len(keys)): + subtree = keys[("%d" % i)] + yield repo, subtree + +def _subtreegen(ui, repo, opts): + """yields (repo, subtree) tuples""" + l = _subtreelist(ui, repo, opts) + namespace = _ns(ui, opts) + yielded = False + if l: + for subtree in l: + # Look for file://..., http://..., ssh://..., etc. + if subtree.split(':', 2)[0] in hg.schemes: + if not yielded: + for r, st in _subtreegen_listkeys(ui, repo, opts, namespace): + yield r, st + repo = hg_repo(ui, subtree, opts) + yielded = False + else: + yielded = True + yield repo, subtree + if not yielded: + for r, st in _subtreegen_listkeys(ui, repo, opts, namespace): + yield r, st + +def _docmd1(cmd, ui, repo, *args, **opts): + """Call cmd for repo and each configured/specified subtree. + + This is for commands which operate on a single tree (e.g., tstatus, + tupdate).""" + + ui.status('[%s]:\n' % repo.root) + # XXX - should be done just once. + cmdopts = dict(opts) + for o in subtreesopts: + if o[1] in cmdopts: + del cmdopts[o[1]] + trc = cmd(ui, repo, *args, **cmdopts) + rc = trc != None and trc or 0 + for subtree in _subtreelist(ui, repo, opts): + ui.status('\n') + lr = hg.repository(ui, repo.wjoin(subtree)) + trc = _docmd1(cmd, lr.ui, lr, *args, **opts) + rc += trc != None and trc or 0 + return rc + +def _docmd2(cmd, ui, repo, remote, adjust, **opts): + """Call cmd for repo and each configured/specified subtree. + + This is for commands which operate on two trees (e.g., tpull, tpush).""" + + ui.status('[%s]:\n' % repo.root) + trc = cmd(ui, repo, remote, **opts) + rc = trc != None and trc or 0 + 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 + trc = _docmd2(cmd, lr.ui, lr, remote2, adjust, **opts) + rc += trc != None and trc or 0 + return rc + +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 _origcmd(name): + """Return the callable mercurial will invoke for the given command name.""" + return cmdutil.findcmd(name, commands.table)[1][0] + +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 _stripfilescheme(url): + if url.startswith('file:'): + i = 5 + while url[i:i+1] == '//': + i += 1 + url = url[i:] + return url + +def _subtreejoin(repo, subtree): + """Return a string (url or path) referring to subtree within repo.""" + u = _stripfilescheme(repo.url()).rstrip('/') + return u + '/' + 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 _readfile(path): + f = None + try: + f = open(path, 'r') + s = f.read() + f.close() + return s + except: + if f: + f.close() + return None + +def _writeconfig(repo, namespace, subtrees, append = False): + confpath = repo.join(namespace or 'trees') + if subtrees: + newconfig = '\n'.join(subtrees) + '\n' + if append or newconfig != _readfile(confpath): + f = open(confpath, append and 'a' or 'w') + try: + f.write(newconfig) + finally: + f.close() + elif os.path.exists(confpath): + os.remove(confpath) + return 0 + +# ---------------- commands and associated recursion helpers ------------------- + +def _clonerepo(ui, source, dest, opts): + _makeparentdir(dest) + # Copied from mercurial/hg.py; need the returned dest repo. + s, d = hg_clone(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')) + if isinstance(s, localrepo.localrepository) or isinstance(d.local(), bool): + return (s, d) + # peers; return the destination localrepo + return (s, d.local()) + +def _skiprepo(ui, source, dest): + src = None + try: + src = hg_repo(ui, source, {}) + except: + class fakerepo(object): + def __init__(self, ui, path): + self.ui = ui + self._path = path + def peer(self): + return self + def local(self): + return self._path + def url(self): + return self._path + def wjoin(self, path): + return os.path.join(self._path, path) + src = fakerepo(ui, source) + return (src, hg.repository(ui, dest)) + +def _clonesubtrees(ui, src, dst, opts): + 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) + return subtrees + +def _clone(ui, source, dest, opts, skiproot = False): + if not skiproot and not os.path.exists(os.path.join(dest, '.hg')): + ui.status('cloning %s\n' % source) + src, dst = _clonerepo(ui, source, dest, opts) + ui.status(_('created %s\n') % dst.root) + else: + msg = 'skipping %s (destination exists)\n' + if skiproot: + msg = 'skipping root %s\n' + ui.status(msg % source) + src, dst = _skiprepo(ui, source, dest) + subtrees = _clonesubtrees(ui, src, dst, opts) + addconfig(ui, dst, subtrees, opts, True) + +# Need to indirect through hg_clone for compatibility w/various hg versions. +hg_clone = None + +def clone(ui, source, dest=None, *subtreeargs, **opts): + '''copy one or more existing repositories to create a tree''' + global hg_clone + if not hg_clone: + hg_clone = compatible_clone() + if subtreeargs: + s = __builtin__.list(subtreeargs) + s.extend(opts.get('subtrees')) # Note: extend does not return a value + opts['subtrees'] = s + if dest is None: + dest = hg.defaultdest(source) + _clone(ui, source, dest, opts, opts.get('skiproot')) + 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. + + The command is executed directly (i.e., not using a shell), so if i/o + redirection or other shell features are desired, include the shell + invocation in the command, e.g.: hg tcommand -- sh -c 'ls -l > ls.out' + + Mercurial 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 commit(ui, repo, *pats, **opts): + """commit all files""" + _checklocal(repo) + if pats: + util.Abort('must commit all files') + + hgcommit = _origcmd('commit') + def condcommit(ui, repo, *pats, **opts): + '''commit conditionally - only if there is something to commit''' + for l in repo.status()[:3]: # modified, added, removed + if l: + return hgcommit(ui, repo, *pats, **opts) + ui.status('nothing to commit\n') + return 0 + + return _docmd1(condcommit, ui, repo, *pats, **opts) + +def addconfig(ui, repo, subtrees, opts, ignoredups = False): + modified = False + l = _subtreelist(ui, repo, opts) + for subtree in subtrees: + if subtree in l: + if not ignoredups: + raise util.Abort(_('subtree %s already configured' % subtree)) + else: + l += [subtree] + modified = True + if modified: + return _writeconfig(repo, _ns(ui, opts), l) + return 0 + +def delconfig(ui, repo, subtrees, opts): + all = opts.get('all') + if all and subtrees: + raise util.Abort(_('use either --all or subtrees (but not both)')) + if all: + return _writeconfig(repo, _ns(ui, opts), []) + 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, _ns(ui, opts), l) + +def expandconfig(ui, repo, args, opts): + """show recursively-expanded trees config items + + Config items in the [trees] section can be defined in terms of other items; + this command shows the expanded value. + + returns 0 if at least one config item was found; otherwise returns 1. + """ + + rc = 1 + for item in args: + rhs = ui.configlist('trees', item) + if rhs: + rc = 0 + l = _expandsubtrees(ui, rhs) + ui.write(' '.join(l)) + ui.write('\n') + return rc + +def _depthmostsplit(subtreemap, subtree): + repo, sub = os.path.split(subtree) + while repo: + if repo in subtreemap: + return repo, sub + repo, sub2 = os.path.split(repo) + sub = subtree[len(repo) + 1:] + return '.', subtree + +def nestconfig(ui, repo, subtrees, opts): + newtrees = { } + subtreemap = dict.fromkeys(subtrees) + for sub in subtrees: + nestedrepo, nestedsub = _depthmostsplit(subtreemap, sub) + if nestedrepo in newtrees: + nl = newtrees[nestedrepo] + if nestedsub not in nl: + nl.append(nestedsub) + else: + newtrees[nestedrepo] = [nestedsub] + for sub in subtrees: + nr = hg_repo(ui, _subtreejoin(repo, sub), {}) + _writeconfig(nr, _ns(ui, opts), newtrees.get(sub)) + return _writeconfig(repo, _ns(ui, opts), newtrees.get('.')) + +# tconfig --set --depth example:: +# +# before after +# ------------ -------------------- +# $ hg tconfig $ hg tconfig +# sub1 sub1 +# sub1/sub1.1 sub2 +# sub2 $ hg -R sub1 tconfig +# sub2/sub2.1 sub1.1 +# sub2/sub2.2 $ hg -R sub2 tconfig +# sub2.1 +# sub2.2 + +def setconfig(ui, repo, subtrees, opts): + walk = opts.get('walk') + depth = opts.get('depth') + if walk and subtrees: + msg = _('subtrees cannot be specified when --walk is used') + raise util.Abort(msg) + elif not (subtrees or walk or depth): + msg = _('specify subtrees, or use --walk and/or --depth') + raise util.Abort(msg) + + if walk: + subtrees = _shortpaths(repo.root, _walk(ui, repo, {}))[1:] + elif not subtrees: + subtrees = _shortpaths(repo.root, _list(ui, repo, opts))[1:] + if depth: + return nestconfig(ui, repo, subtrees, opts) + return _writeconfig(repo, _ns(ui, opts), subtrees) + +def config(ui, repo, *subtrees, **opts): + """list or change the subtrees configuration + + One of five operations can be selected: + + --list: list the configured subtrees; this is the default if no other + operation is selected. + + --add: add the specified subtrees to the configuration. + + --del: delete the specified subtrees from the configuration. + Use --del --all to delete all subtrees. + + --set: set the subtree configuration to the specified subtrees. + Use --set --walk to walk the filesystem rooted at REPO and set the + subtree configuration to the discovered repos. Use --depth + to write the subtree configuration depth-most, so that each + subtree is defined within the nearest enclosing repository. Note + that --walk and --depth may be used together. + + --expand: list the value of config items from the [trees] section. + Items in the [trees] section can be defined in terms of other + items in the [trees] section; tconfig --expand shows the + recursively expanded value. It returns 0 if at least one config + item was found; otherwise it returns 1. + + Note that with the slight exception of --set --depth, this command + does not recurse into subtrees; it operates only on the current + repository. (To recursively list subtrees, use the tlist command.) + + """ + + opadd = opts.get('add') + opdel = opts.get('del') + opexp = opts.get('expand') + oplst = opts.get('list') + opset = opts.get('set') + cnt = opadd + opdel + opexp + oplst + opset + if cnt > 1: + raise util.Abort(_('at most one of --add, --del, --list, ' + + '--set or --expand is allowed')) + if not opexp and not repo: + raise util.Abort(_('no repository found')) + if repo: + _checklocal(repo) + + if opadd: + return addconfig(ui, repo, subtrees, opts) + if opdel: + return delconfig(ui, repo, subtrees, opts) + if opexp: + return expandconfig(ui, repo, subtrees, opts) + if opset: + return setconfig(ui, repo, subtrees, opts) + + for subtree in _subtreelist(ui, repo, opts): + ui.write(subtree + '\n') + return 0 + +def diff(ui, repo, *args, **opts): + """diff repository (or selected files)""" + _checklocal(repo) + return _docmd1(_origcmd('diff'), ui, repo, *args, **opts) + +def heads(ui, repo, *branchrevs, **opts): + """show current repository heads or show branch heads""" + _checklocal(repo) + st = opts.get('subtrees') + repocount = len(_list(ui, repo, st and {'subtrees': st} or {})) + rc = _docmd1(_origcmd('heads'), ui, repo, *branchrevs, **opts) + # return 0 if any of the repos have matching heads; 1 otherwise. + return int(rc == repocount) + +def incoming(ui, repo, remote="default", **opts): + """show new changesets found in source""" + _checklocal(repo) + adjust = remote and not ui.config('paths', remote) + st = opts.get('subtrees') + repocount = len(_list(ui, repo, st and {'subtrees': st} or {})) + rc = _docmd2(_origcmd('incoming'), ui, repo, remote, adjust, **opts) + # return 0 if any of the repos have incoming changes; 1 otherwise. + return int(rc == repocount) + +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 configuration. + + If the --walk option is specified, search the filesystem instead of using + the command line or repo configuration. + + If the --short option is specified, the listed paths are 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 log(ui, repo, *args, **opts): + '''show revision history of entire repository or files''' + _checklocal(repo) + return _docmd1(_origcmd('log'), ui, repo, *args, **opts) + +def merge(ui, repo, node=None, **opts): + '''merge working directory with another revision''' + _checklocal(repo) + + hgmerge = _origcmd('merge') + def condmerge(ui, repo, node=None, **opts): + if len(repo.heads()) > 1: + return hgmerge(ui, repo, node, **opts) + ui.status('nothing to merge\n') + return 0 + + return _docmd1(condmerge, ui, repo, node, **opts) + +def outgoing(ui, repo, remote=None, **opts): + '''show changesets not found in the destination''' + _checklocal(repo) + adjust = remote and not ui.config('paths', remote) + st = opts.get('subtrees') + repocount = len(_list(ui, repo, st and {'subtrees': st} or {})) + rc = _docmd2(_origcmd('outgoing'), ui, repo, remote, adjust, **opts) + # return 0 if any of the repos have outgoing changes; 1 otherwise. + return int(rc == repocount) + +def parents(ui, repo, filename=None, **opts): + _checklocal(repo) + return _docmd1(_origcmd('parents'), ui, repo, filename, **opts) + +def _paths(cmd, ui, repo, search=None, **opts): + ui.status('[%s]:\n' % repo.root) + cmd(ui, repo, search) + for subtree in _subtreelist(ui, repo, opts): + ui.status('\n') + lr = hg.repository(ui, repo.wjoin(subtree)) + _paths(cmd, lr.ui, lr, search, **opts) + return 0 + +def paths(ui, repo, search=None, **opts): + '''show aliases for remote repositories''' + _checklocal(repo) + return _paths(_origcmd('paths'), ui, repo, search, **opts) + +def pull(ui, repo, remote="default", **opts): + '''pull changes from the specified source''' + _checklocal(repo) + adjust = remote and not ui.config('paths', remote) + st = opts.get('subtrees') + repocount = len(_list(ui, repo, st and {'subtrees': st} or {})) + rc = _docmd2(_origcmd('pull'), ui, repo, remote, adjust, **opts) + # Sadly, pull returns 1 if there was nothing to pull *or* if there are + # unresolved files on update. No way to distinguish between them. + # return 0 if any subtree pulled successfully. + return int(rc == repocount) + +def push(ui, repo, remote=None, **opts): + '''push changes to the specified destination''' + _checklocal(repo) + adjust = remote and not ui.config('paths', remote) + st = opts.get('subtrees') + repocount = len(_list(ui, repo, st and {'subtrees': st} or {})) + rc = _docmd2(_origcmd('push'), ui, repo, remote, adjust, **opts) + # return 0 if all pushes were successful; 1 if none of the repos had + # anything to push. + return int(rc == repocount) + +def status(ui, repo, *args, **opts): + '''show changed files in the working directory''' + _checklocal(repo) + return _docmd1(_origcmd('status'), ui, repo, *args, **opts) + +def summary(ui, repo, **opts): + """summarize working directory state""" + _checklocal(repo) + return _docmd1(_origcmd('summary'), ui, repo, **opts) + +def tag(ui, repo, name1, *names, **opts): + '''add one or more tags for the current or given revision''' + _checklocal(repo) + return _docmd1(_origcmd('tag'), ui, repo, name1, *names, **opts) + +def tip(ui, repo, **opts): + '''show the tip revision''' + _checklocal(repo) + return _docmd1(_origcmd('tip'), ui, repo, **opts) + +def _update(cmd, ui, repo, node=None, rev=None, clean=False, date=None, + check=False, **opts): + ui.status('[%s]:\n' % repo.root) + if _newupdate: + trc = cmd(ui, repo, node, rev, clean, date, check) + else: + trc = cmd(ui, repo, node, rev, clean, date) + rc = trc != None and trc or 0 + for subtree in _subtreelist(ui, repo, opts): + ui.status('\n') + lr = hg.repository(ui, repo.wjoin(subtree)) + trc = _update(cmd, lr.ui, lr, node, rev, clean, date, check, **opts) + rc += trc != None and trc or 0 + return rc + +def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False, + **opts): + '''update working directory (or switch revisions)''' + _checklocal(repo) + rc = _update(_origcmd('update'), ui, repo, node, rev, clean, date, check, + **opts) + return rc and 1 or 0 + +def version(ui, **opts): + '''show version information''' + ui.status('trees extension (version 0.7)\n') + +def defpath(ui, repo, peer=None, peer_push=None, **opts): + '''examine and manipulate default path settings for a tree.''' + def walker(r): + return _list(ui, r, opts) + return defpath_mod.defpath(ui, repo, peer, peer_push, walker, opts) + +def debugkeys(ui, src, **opts): + '''list the tree configuration using mercurial's pushkey mechanism. + + This works for remote repositories as long as the remote hg server has the + trees extension enabled.''' + d = hg_repo(ui, src, opts).listkeys(_ns(ui, opts)) + i = 0 + n = len(d) + while i < n: + istr = str(i) + ui.write("%s: %s\n" % (istr, d[istr])) + i += 1 + return 0 + +# ----------------------------- mercurial linkage ------------------------------ + +if not hasattr(hg, 'remoteui'): + if hasattr(cmdutil, 'remoteui'): + # hg < 1.5.4: remoteui is in cmdutil instead of hg + hg.remoteui = cmdutil.remoteui + else: + # hg < 1.3: no remoteui + def _remoteui(ui, opts): + cmdutil.setremoteconfig(ui, opts) + return ui + hg.remoteui = _remoteui + +# Tolerate changes to the signature of hg.clone(). +def compatible_clone(): + clone_args = inspect.getargspec(hg.clone)[0] + if not 'branch' in clone_args: + # hg < 1.5: no 'branch' parameter (a78bfaf988e1) + def hg_clone(ui, peeropts, source, dest=None, pull=False, rev=None, + update=True, stream=False, branch=None): + rui = hg.remoteui(ui, peeropts) + return hg.clone(rui, source, dest, pull, rev, update, stream) + return hg_clone + if len(clone_args) < 9: + # hg < 1.9: no 'peeropts' parameter (d976542986d2, bd1acea552ff). + def hg_clone(ui, peeropts, source, dest=None, pull=False, rev=None, + update=True, stream=False, branch=None): + rui = hg.remoteui(ui, peeropts) + return hg.clone(rui, source, dest, pull, rev, update, stream, + branch) + return hg_clone + return hg.clone + +# hg < 2.3: no peer() method +if getattr(hg, 'peer', None) is None: + hg.peer = lambda ui, opts, url: hg.repository(ui, url) + +_newupdate = len(inspect.getargspec(commands.update)[0]) >= 7 +namespaceopt = [('', 'tns', '', + _('trees namespace to use'), + _('NAMESPACE'))] +subtreesopts = [('', 'subtrees', [], + _('path to subtree'), + _('SUBTREE'))] + namespaceopt + +if len(commands.globalopts[0]) < 5: + # hg < 1.5.4: arg description (5th tuple element) is not supported + def trimoptions(l): + i = 0 + for opt in l: + l[i] = opt[:4] + i += 1 + trimoptions(namespaceopt) + trimoptions(subtreesopts) + +walkopt = [('w', 'walk', False, + _('walk the filesystem to discover subtrees'))] + +cloneopts = [('', 'skiproot', False, + _('do not clone the root repo in the tree')) + ] + subtreesopts +commandopts = [('', 'stop', False, + _('stop if command returns non-zero')) + ] + subtreesopts +listopts = [('s', 'short', False, + _('list short paths (relative to repo root)')) + ] + walkopt + subtreesopts +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')), + ('e', 'expand', False, + _('recursively expand config items in the [trees] section')), + ('l', 'list', False, + _('list the configured subtrees')), + ('', 'depth', False, + _('store subtree configuration depth-most')), + ('s', 'set', False, _('set the subtree config to SUBTREEs')) + ] + namespaceopt + walkopt + +def _newcte(origcmd, newfunc, extraopts = [], synopsis = None): + '''generate a cmdtable entry based on that for origcmd''' + cte = cmdutil.findcmd(origcmd, commands.table)[1] + # Filter out --exclude and --include, since those do not work across + # repositories (mercurial converts them to abs paths). + opts = [o for o in cte[1] if o[1] not in ('exclude', 'include')] + if len(cte) > 2: + return (newfunc, opts + extraopts, synopsis or cte[2]) + return (newfunc, opts + extraopts, synopsis) + +def extsetup(ui = None): + # The cmdtable is initialized here to pick up options added by other + # extensions (e.g., rebase, bookmarks). + # + # Commands tagged with '^' are listed by 'hg help'. + global defpath_mod + defpath_mod = None + defpath_opts = [] + try: + defpath_mod = extensions.find('defpath') + defpath_opts = __builtin__.list(defpath_mod.opts) + subtreesopts + defpath_doc = getattr(defpath_mod, 'common_docstring', '') + if defpath_doc: + defpath.__doc__ += defpath_doc + except: + pass + + global cmdtable + cmdtable = { + '^tclone': _newcte('clone', clone, cloneopts, + _('[OPTION]... SOURCE [DEST [SUBTREE]...]')), + 'tcommand|tcmd': (command, commandopts, _('command [arg] ...')), + 'tcommit|tci': _newcte('commit', commit, subtreesopts), + 'tconfig': (config, configopts, _('[OPTION]... [SUBTREE]...')), + 'tdiff': _newcte('diff', diff, subtreesopts), + 'theads': _newcte('heads', heads, subtreesopts), + 'tincoming': _newcte('incoming', incoming, subtreesopts), + 'toutgoing': _newcte('outgoing', outgoing, subtreesopts), + 'tlist': (list, listopts, _('[OPTION]...')), + '^tlog|thistory': _newcte('log', log, subtreesopts), + 'tmerge': _newcte('merge', merge, subtreesopts), + 'tparents': _newcte('parents', parents, subtreesopts), + 'tpaths': _newcte('paths', paths, subtreesopts), + '^tpull': _newcte('pull', pull, subtreesopts), + '^tpush': _newcte('push', push, subtreesopts), + '^tstatus': _newcte('status', status, subtreesopts), + '^tupdate': _newcte('update', update, subtreesopts), + 'ttag': _newcte('tag', tag, subtreesopts), + 'ttip': _newcte('tip', tip, subtreesopts), + 'tversion': (version, [], ''), + 'tdebugkeys': (debugkeys, namespaceopt, '') + } + if defpath_mod: + cmdtable['tdefpath'] = (defpath, defpath_opts, _('')) + if getattr(commands, 'summary', None): + cmdtable['tsummary'] = _newcte('summary', summary, subtreesopts) + +commands.norepo += ' tclone tversion tdebugkeys' +commands.optionalrepo += ' tconfig' + +def genlistkeys(namespace): + def _listkeys(repo): + # trees are ordered, so the keys are the non-negative integers. + d = {} + i = 0 + try: + for line in repo.opener(namespace): + d[("%d" % i)] = line.rstrip('\n\r') + i += 1 + return d + except: + return {} + return _listkeys + +def reposetup(ui, repo): + # Pushing keys is disabled; unclear whether/how it should work. + pushfunc = lambda *x: False + x = [_nsnormalize(s) for s in ui.configlist('trees', 'namespaces', [])] + try: + for ns in [_ns(ui, {})] + x: + pushkey.register(ns, pushfunc, genlistkeys(ns)) + except exceptions.ImportError: + # hg < 1.6 - no pushkey. + def _listkeys(self, namespace): + # trees are ordered, so the keys are the non-negative integers. + d = {} + i = 0 + try: + for line in self.opener(namespace): + d[("%d" % i)] = line.rstrip('\n\r') + i += 1 + return d + except: + return {} + setattr(type(repo), 'listkeys', _listkeys) |