inotify: fix issue1375, add a test.
The biggest problem was the data structure, which did not allow changing
a file into a directory or vice versa. This problem is fixed by b55d44719b47.
The walk() method also had an issue in this case:
- we know 'path' as a directory. inotify server sleeps.
- 'path' is deleted
- 'path' is recreated as a file
- the server catches up here, and see the deletion. it instantiates a scan(),
which in its turn calls for walk(repo, path).
- walk() then assumes that 'path' is a directory and calls os.listdir on it,
which raises an OSError(errno.ENOTDIR)
Catch the error, and yield the file instead of the directory contents.
# __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]...')),
}