Mercurial > public > mercurial-scm > hg
comparison mercurial/commands.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 | 1e28a7f58f33 |
children | fa6d5c62f3bd |
comparison
equal
deleted
inserted
replaced
18791:d844e3879f9b | 18792:10669e24eb6c |
---|---|
2134 ui.write(hex(repl)) | 2134 ui.write(hex(repl)) |
2135 ui.write(' %X ' % m._data[2]) | 2135 ui.write(' %X ' % m._data[2]) |
2136 ui.write('{%s}' % (', '.join('%r: %r' % t for t in | 2136 ui.write('{%s}' % (', '.join('%r: %r' % t for t in |
2137 sorted(m.metadata().items())))) | 2137 sorted(m.metadata().items())))) |
2138 ui.write('\n') | 2138 ui.write('\n') |
2139 | |
2140 @command('debugpathcomplete', | |
2141 [('f', 'full', None, _('complete an entire path')), | |
2142 ('n', 'normal', None, _('show only normal files')), | |
2143 ('a', 'added', None, _('show only added files')), | |
2144 ('r', 'removed', None, _('show only removed files'))], | |
2145 _('FILESPEC...')) | |
2146 def debugpathcomplete(ui, repo, *specs, **opts): | |
2147 '''complete part or all of a tracked path | |
2148 | |
2149 This command supports shells that offer path name completion. It | |
2150 currently completes only files already known to the dirstate. | |
2151 | |
2152 Completion extends only to the next path segment unless | |
2153 --full is specified, in which case entire paths are used.''' | |
2154 | |
2155 def complete(path, acceptable): | |
2156 dirstate = repo.dirstate | |
2157 spec = os.path.normpath(os.path.join(os.getcwd(), path)) | |
2158 rootdir = repo.root + os.sep | |
2159 if spec != repo.root and not spec.startswith(rootdir): | |
2160 return [], [] | |
2161 if os.path.isdir(spec): | |
2162 spec += '/' | |
2163 spec = spec[len(rootdir):] | |
2164 fixpaths = os.sep != '/' | |
2165 if fixpaths: | |
2166 spec = spec.replace(os.sep, '/') | |
2167 speclen = len(spec) | |
2168 fullpaths = opts['full'] | |
2169 files, dirs = set(), set() | |
2170 adddir, addfile = dirs.add, files.add | |
2171 for f, st in dirstate.iteritems(): | |
2172 if f.startswith(spec) and st[0] in acceptable: | |
2173 if fixpaths: | |
2174 f = f.replace('/', os.sep) | |
2175 if fullpaths: | |
2176 addfile(f) | |
2177 continue | |
2178 s = f.find(os.sep, speclen) | |
2179 if s >= 0: | |
2180 adddir(f[:s+1]) | |
2181 else: | |
2182 addfile(f) | |
2183 return files, dirs | |
2184 | |
2185 acceptable = '' | |
2186 if opts['normal']: | |
2187 acceptable += 'nm' | |
2188 if opts['added']: | |
2189 acceptable += 'a' | |
2190 if opts['removed']: | |
2191 acceptable += 'r' | |
2192 cwd = repo.getcwd() | |
2193 if not specs: | |
2194 specs = ['.'] | |
2195 | |
2196 files, dirs = set(), set() | |
2197 for spec in specs: | |
2198 f, d = complete(spec, acceptable or 'nmar') | |
2199 files.update(f) | |
2200 dirs.update(d) | |
2201 for d in dirs: | |
2202 files.add(d + 'a') | |
2203 files.add(d + 'b') | |
2204 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files))) | |
2205 ui.write('\n') | |
2139 | 2206 |
2140 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]')) | 2207 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]')) |
2141 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts): | 2208 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts): |
2142 '''access the pushkey key/value protocol | 2209 '''access the pushkey key/value protocol |
2143 | 2210 |