view hgext/inotify/__init__.py @ 9115:b55d44719b47

inotify: server: new data structure to keep track of changes. == Rationale for the new structure == Current structure was a dictionary tree. One directory was tracked as a dictionary: - keys: file/subdir name - values: - for a file, the status (a/r/m/...) - for a subdir, the directory representing the subdir It allowed efficient lookups, no matter of the type of the terminal leaf: for part in path.split('/'): tree = tree[part] However, there is no way to represent a directory and a file with the same name because keys are conflicting in the dictionary. Concrete example: Initial state: root dir |- foo (file) |- bar (file) # data state is: {'foo': 'n', 'bar': 'n'} Remove foo: root dir |- bar (file) # Data becomes {'foo': 'r'} until next commit. Add foo, as a directory, and foo/barbar file: root dir |- bar (file) |-> foo (dir) |- barbar (file) # New state should be represented as: {'foo': {'barbar': 'a'}, 'bar': 'n'} however, the key "foo" is already used and represents the old file. The dirstate: D foo A foo/barbar cannot be represented, hence the need for a new structure. == The new structure == 'directory' class. Represents one directory level. * Notable attributes: Two dictionaries: - 'files' Maps filename -> status for the current dir. - 'dirs' Maps subdir's name -> directory object representing the subdir * methods - walk(), formerly server.walk - lookup(), old server.lookup - dir(), old server.dir This new class allows embedding all the tree walks/lookups in its own class, instead of having everything mixed together in server. Incidently, since files and directories are not stored in the same dictionaries, we are solving the previous key conflict problem. The small drawback is that lookup operation is a bit more complex: for a path a/b/c/d/e we have to check twice the leaf, if e is a directory or a file.
author Nicolas Dumazet <nicdumz.commits@gmail.com>
date Tue, 26 May 2009 23:00:35 +0900
parents 9cda78218ab3
children 206f7f4c5c2a
line wrap: on
line source

# __init__.py - inotify-based status acceleration for Linux
#
# Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
# Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

'''accelerate status report using Linux's inotify service'''

# todo: socket permissions

from mercurial.i18n import _
from mercurial import cmdutil, util
import server
from weakref import proxy
from client import client, QueryFailed

def serve(ui, repo, **opts):
    '''start an inotify server for this repository'''
    timeout = opts.get('timeout')
    if timeout:
        timeout = float(timeout) * 1e3

    class service(object):
        def init(self):
            try:
                self.master = server.master(ui, repo, timeout)
            except server.AlreadyStartedException, inst:
                raise util.Abort(str(inst))

        def run(self):
            try:
                self.master.run()
            finally:
                self.master.shutdown()

    service = service()
    logfile = ui.config('inotify', 'log')
    cmdutil.service(opts, initfn=service.init, runfn=service.run,
                    logfile=logfile)

def debuginotify(ui, repo, **opts):
    '''debugging information for inotify extension

    Prints the list of directories being watched by the inotify server.
    '''
    cli = client(ui, repo)
    response = cli.debugquery()

    ui.write(_('directories being watched:\n'))
    for path in response:
        ui.write(('  %s/\n') % path)

def reposetup(ui, repo):
    if not hasattr(repo, 'dirstate'):
        return

    # XXX: weakref until hg stops relying on __del__
    repo = proxy(repo)

    class inotifydirstate(repo.dirstate.__class__):

        # We'll set this to false after an unsuccessful attempt so that
        # next calls of status() within the same instance don't try again
        # to start an inotify server if it won't start.
        _inotifyon = True

        def status(self, match, ignored, clean, unknown=True):
            files = match.files()
            if '.' in files:
                files = []
            if self._inotifyon and not ignored:
                cli = client(ui, repo)
                try:
                    result = cli.statusquery(files, match, False,
                                            clean, unknown)
                except QueryFailed, instr:
                    ui.debug(str(instr))
                    # don't retry within the same hg instance
                    inotifydirstate._inotifyon = False
                    pass
                else:
                    if ui.config('inotify', 'debug'):
                        r2 = super(inotifydirstate, self).status(
                            match, False, clean, unknown)
                        for c,a,b in zip('LMARDUIC', result, r2):
                            for f in a:
                                if f not in b:
                                    ui.warn('*** inotify: %s +%s\n' % (c, f))
                            for f in b:
                                if f not in a:
                                    ui.warn('*** inotify: %s -%s\n' % (c, f))
                        result = r2
                    return result
            return super(inotifydirstate, self).status(
                match, ignored, clean, unknown)

    repo.dirstate.__class__ = inotifydirstate

cmdtable = {
    'debuginotify':
        (debuginotify, [], ('hg debuginotify')),
    '^inserve':
        (serve,
         [('d', 'daemon', None, _('run server in background')),
          ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
          ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
          ('', 'pid-file', '', _('name of file to write process ID to'))],
         _('hg inserve [OPTION]...')),
    }