[PATCH 1 of 2 RFC] completion: add a debugpathcomplete command

Bryan O'Sullivan bos at serpentine.com
Wed Mar 13 16:42:53 CDT 2013


# HG changeset patch
# User Bryan O'Sullivan <bryano at fb.com>
# Date 1363210776 25200
# Node ID 8d53d4c8d0e5b0b59fc8dc1adb51802ec621975f
# Parent  cd2c82510aa230585fa50736a9e05f169c812dad
completion: add a debugpathcomplete command

The bash_completion code uses "hg status" to generate a list of
possible completions for commands that operate in the working
directory. In a large working directory, this results in a single
tab-completion being very slow (several seconds) as a result of
checking the status of every file, even when there are *no* possible
matches.

The new debugpathcomplete command does not check the status of
files, and is specialized to only complete pathnames using the
contents of the dirstate.

Since it never touches the working directory, it is much faster in
e.g. mozilla-central (~65,000 files):

  debugpathcomplete  0.17
  status             0.85

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -2109,6 +2109,66 @@ def debugobsolete(ui, repo, precursor=No
                                          sorted(m.metadata().items()))))
             ui.write('\n')
 
+ at command('debugpathcomplete',
+         [('', 'full', None, _('complete an entire path')),
+          ('n', 'normal', None, _('show only normal files')),
+          ('a', 'added', None, _('show only added files')),
+          ('r', 'removed', None, _('show only removed files'))],
+         _('FILESPEC...'))
+def debugpathcomplete(ui, repo, *specs, **opts):
+    '''complete part or all of a tracked path
+
+    This command supports shells that offer path name completion. It
+    currently completes only files already known to the dirstate.
+
+    Completion extends only to the next pathname segment unless
+    --full is specified.'''
+
+    def complete(path, acceptable):
+        dirstate = repo.dirstate
+        spec = os.path.normpath(os.path.join(os.getcwd(), path))
+        if not spec.startswith(dirstate._rootdir):
+            return []
+        if os.path.isdir(spec):
+            spec += '/'
+        spec = spec[len(dirstate._rootdir):]
+        speclen = len(spec)
+        if opts['full']:
+            return sorted(f for f in dirstate._map
+                          if (f.startswith(spec) and
+                              dirstate._map[f][0] in acceptable))
+        matches = set()
+        for f in dirstate._map:
+            if f.startswith(spec) and dirstate._map[f][0] in acceptable:
+                s = f.find('/', speclen)
+                if os.sep != '/':
+                    f = f.replace('/', os.sep)
+                if s >= 0:
+                    f = f[:s+1]
+                    # if a match is a directory, force the shell to
+                    # consider the match as ambiguous, otherwise it
+                    # will append a trailing space
+                    matches.add(f + 'a')
+                    matches.add(f + 'b')
+                else:
+                    matches.add(f)
+        return sorted(matches)
+
+    acceptable = ''
+    if opts['normal']:
+        acceptable += 'nm'
+    if opts['added']:
+        acceptable += 'a'
+    if opts['removed']:
+        acceptable += 'r'
+    cwd = repo.getcwd()
+    if not specs:
+        specs = ['.']
+
+    for spec in specs:
+        for p in complete(spec, acceptable or 'nmar?'):
+            ui.write(repo.pathto(p, cwd) + '\n')
+
 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
     '''access the pushkey key/value protocol


More information about the Mercurial-devel mailing list