Mercurial > hg
view mercurial/treediscovery.py @ 18792:10669e24eb6c
completion: add a debugpathcomplete command
The bash_completion code uses "hg status" to generate a list of
possible completions for commands that operate on files in the
working directory. In a large working directory, this can result
in a single tab-completion being very slow (several seconds) as a
result of checking the status of every file, even when there is no
need to check status or no possible matches.
The new debugpathcomplete command gains performance in a few simple
ways:
* Allow completion to operate on just a single directory. When used
to complete the right commands, this considerably reduces the
number of completions returned, at no loss in functionality.
* Never check the status of files. For completions that really must
know if a file is modified, it is faster to use status:
hg status -nm 'glob:myprefix**'
Performance:
Here are the commands used by bash_completion to complete, run in
the root of the mozilla-central working dir (~77,000 files) and
another repo (~165,000 files):
All "normal state" files (used by e.g. remove, revert):
mozilla other
status -nmcd 'glob:**' 1.77 4.10 sec
debugpathcomplete -f -n 0.53 1.26
debugpathcomplete -n 0.17 0.41
("-f" means "complete full paths", rather than the current directory)
Tracked files matching "a":
mozilla other
status -nmcd 'glob:a**' 0.26 0.47
debugpathcomplete -f -n a 0.10 0.24
debugpathcomplete -n a 0.10 0.22
We should be able to further improve completion performance once
the critbit work lands. Right now, our performance is limited by
the need to iterate over all keys in the dirstate.
author | Bryan O'Sullivan <bryano@fb.com> |
---|---|
date | Thu, 21 Mar 2013 16:31:28 -0700 |
parents | cafd8a8fb713 |
children | d2704c48f417 |
line wrap: on
line source
# discovery.py - protocol changeset discovery functions # # Copyright 2010 Matt Mackall <mpm@selenic.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from node import nullid, short from i18n import _ import util, error def findcommonincoming(repo, remote, heads=None, force=False): """Return a tuple (common, fetch, heads) used to identify the common subset of nodes between repo and remote. "common" is a list of (at least) the heads of the common subset. "fetch" is a list of roots of the nodes that would be incoming, to be supplied to changegroupsubset. "heads" is either the supplied heads, or else the remote's heads. """ m = repo.changelog.nodemap search = [] fetch = set() seen = set() seenbranch = set() base = set() if not heads: heads = remote.heads() if repo.changelog.tip() == nullid: base.add(nullid) if heads != [nullid]: return [nullid], [nullid], list(heads) return [nullid], [], heads # assume we're closer to the tip than the root # and start by examining the heads repo.ui.status(_("searching for changes\n")) unknown = [] for h in heads: if h not in m: unknown.append(h) else: base.add(h) if not unknown: return list(base), [], list(heads) req = set(unknown) reqcnt = 0 # search through remote branches # a 'branch' here is a linear segment of history, with four parts: # head, root, first parent, second parent # (a branch always has two parents (or none) by definition) unknown = util.deque(remote.branches(unknown)) while unknown: r = [] while unknown: n = unknown.popleft() if n[0] in seen: continue repo.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1]))) if n[0] == nullid: # found the end of the branch pass elif n in seenbranch: repo.ui.debug("branch already found\n") continue elif n[1] and n[1] in m: # do we know the base? repo.ui.debug("found incomplete branch %s:%s\n" % (short(n[0]), short(n[1]))) search.append(n[0:2]) # schedule branch range for scanning seenbranch.add(n) else: if n[1] not in seen and n[1] not in fetch: if n[2] in m and n[3] in m: repo.ui.debug("found new changeset %s\n" % short(n[1])) fetch.add(n[1]) # earliest unknown for p in n[2:4]: if p in m: base.add(p) # latest known for p in n[2:4]: if p not in req and p not in m: r.append(p) req.add(p) seen.add(n[0]) if r: reqcnt += 1 repo.ui.progress(_('searching'), reqcnt, unit=_('queries')) repo.ui.debug("request %d: %s\n" % (reqcnt, " ".join(map(short, r)))) for p in xrange(0, len(r), 10): for b in remote.branches(r[p:p + 10]): repo.ui.debug("received %s:%s\n" % (short(b[0]), short(b[1]))) unknown.append(b) # do binary search on the branches we found while search: newsearch = [] reqcnt += 1 repo.ui.progress(_('searching'), reqcnt, unit=_('queries')) for n, l in zip(search, remote.between(search)): l.append(n[1]) p = n[0] f = 1 for i in l: repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i))) if i in m: if f <= 2: repo.ui.debug("found new branch changeset %s\n" % short(p)) fetch.add(p) base.add(i) else: repo.ui.debug("narrowed branch search to %s:%s\n" % (short(p), short(i))) newsearch.append((p, i)) break p, f = i, f * 2 search = newsearch # sanity check our fetch list for f in fetch: if f in m: raise error.RepoError(_("already have changeset ") + short(f[:4])) base = list(base) if base == [nullid]: if force: repo.ui.warn(_("warning: repository is unrelated\n")) else: raise util.Abort(_("repository is unrelated")) repo.ui.debug("found new changesets starting at " + " ".join([short(f) for f in fetch]) + "\n") repo.ui.progress(_('searching'), None) repo.ui.debug("%d total queries\n" % reqcnt) return base, list(fetch), heads