# # 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''' needcommit = len(repo[None].parents()) > 1 # merge if not needcommit: mar = repo.status()[:3] # modified, added, removed needcommit = bool(mar[0] or mar[1] or mar[2]) if needcommit: 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 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 = {} cmd= cmdutil.command(cmdtable) def tcmd(name, origcmd, extraopts = [], synopsis = None, **kwargs): '''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 cmd(name, opts + extraopts, synopsis or cte[2], **kwargs) return cmd(name, opts + extraopts, synopsis, **kwargs) tcmd('^tclone', 'clone', cloneopts, _('[OPTION]... SOURCE [DEST [SUBTREE]...]'), norepo=True)(clone) cmd('tcommand|tcmd', commandopts, _('command [arg] ...'))(command) tcmd('tcommit|tci', 'commit', subtreesopts)(commit) cmd('tconfig', configopts, _('[OPTION]... [SUBTREE]...'), optionalrepo=True)(config) tcmd('tdiff', 'diff', subtreesopts)(diff) tcmd('theads', 'heads', subtreesopts)(heads) tcmd('tincoming', 'incoming', subtreesopts)(incoming) tcmd('toutgoing', 'outgoing', subtreesopts)(outgoing) cmd('tlist', listopts, _('[OPTION]...'))(list) tcmd('^tlog|thistory', 'log', subtreesopts)(log) tcmd('tmerge', 'merge', subtreesopts)(merge) tcmd('tparents', 'parents', subtreesopts)(parents) tcmd('tpaths', 'paths', subtreesopts)(paths) tcmd('^tpull', 'pull', subtreesopts)(pull) tcmd('^tpush', 'push', subtreesopts)(push) tcmd('^tstatus', 'status', subtreesopts)(status) tcmd('^tupdate', 'update', subtreesopts)(update) tcmd('ttag', 'tag', subtreesopts)(tag) tcmd('ttip', 'tip', subtreesopts)(tip) cmd('tversion', [], '', norepo=True)(version) cmd('tdebugkeys', namespaceopt, '', norepo=True)(debugkeys) if defpath_mod: cmd('tdefpath', defpath_opts, _(''))(defpath) if getattr(commands, 'summary', None): tcmd('tsummary', 'summary', subtreesopts)(summary) 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)