##// END OF EJS Templates
subrepo: introduce basic state parsing
subrepo: introduce basic state parsing

File last commit:

r8794:1c610db4 default
r8812:859f8419 default
Show More
server.py
822 lines | 25.7 KiB | text/x-python | PythonLexer
Bryan O'Sullivan
Add inotify extension
r6239 # server.py - inotify status server
#
# Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
# Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
Bryan O'Sullivan
Add inotify extension
r6239
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 from mercurial.i18n import _
Brendan Cully
inotify: remove unused imports (thanks pyflakes)
r7420 from mercurial import osutil, util
Bryan O'Sullivan
Add inotify extension
r6239 import common
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 import errno, os, select, socket, stat, struct, sys, tempfile, time
Bryan O'Sullivan
Add inotify extension
r6239
try:
Brendan Cully
Use relative imports in inotify.server....
r6994 import linux as inotify
from linux import watcher
Bryan O'Sullivan
Add inotify extension
r6239 except ImportError:
raise
class AlreadyStartedException(Exception): pass
def join(a, b):
if a:
if a[-1] == '/':
return a + b
return a + '/' + b
return b
Nicolas Dumazet
inotify: server: move split() out of server...
r8787 def split(path):
c = path.rfind('/')
if c == -1:
return '', path
return path[:c], path[c+1:]
Bryan O'Sullivan
Add inotify extension
r6239 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
def walkrepodirs(repo):
'''Iterate over all subdirectories of this repo.
Exclude the .hg directory, any nested repos, and ignored dirs.'''
rootslash = repo.root + os.sep
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify walking...
r8322
Bryan O'Sullivan
Add inotify extension
r6239 def walkit(dirname, top):
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify...
r8321 fullpath = rootslash + dirname
Bryan O'Sullivan
Add inotify extension
r6239 try:
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify...
r8321 for name, kind in osutil.listdir(fullpath):
Bryan O'Sullivan
Add inotify extension
r6239 if kind == stat.S_IFDIR:
if name == '.hg':
Nicolas Dumazet
inotify: inotify.server.walk*() cleanup...
r8323 if not top:
return
Bryan O'Sullivan
Add inotify extension
r6239 else:
d = join(dirname, name)
if repo.dirstate._ignore(d):
continue
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify walking...
r8322 for subdir in walkit(d, False):
yield subdir
Bryan O'Sullivan
Add inotify extension
r6239 except OSError, err:
if err.errno not in walk_ignored_errors:
raise
Nicolas Dumazet
inotify: inotify.server.walk*() remove unnecessary var...
r8324 yield fullpath
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify walking...
r8322
return walkit('', True)
Bryan O'Sullivan
Add inotify extension
r6239
def walk(repo, root):
'''Like os.walk, but only yields regular files.'''
# This function is critical to performance during startup.
rootslash = repo.root + os.sep
def walkit(root, reporoot):
files, dirs = [], []
try:
fullpath = rootslash + root
for name, kind in osutil.listdir(fullpath):
if kind == stat.S_IFDIR:
if name == '.hg':
Nicolas Dumazet
inotify: inotify.server.walk() simplify control flow
r8325 if not reporoot:
Nicolas Dumazet
inotify: inotify.server.walk*() cleanup...
r8323 return
Nicolas Dumazet
inotify: inotify.server.walk() simplify control flow
r8325 else:
dirs.append(name)
Nicolas Dumazet
inotify: server.walk(): use yield instead of for...
r8381 path = join(root, name)
if repo.dirstate._ignore(path):
continue
for result in walkit(path, False):
yield result
Bryan O'Sullivan
Add inotify extension
r6239 elif kind in (stat.S_IFREG, stat.S_IFLNK):
Nicolas Dumazet
inotify: inotify.server.walk() filetype is never used, do not yield it
r8334 files.append(name)
Nicolas Dumazet
inotify: inotify.server.walk*() remove unnecessary var...
r8324 yield fullpath, dirs, files
Bryan O'Sullivan
Add inotify extension
r6239
except OSError, err:
if err.errno not in walk_ignored_errors:
raise
Nicolas Dumazet
inotify: inotify.server.walk() simplify algorithm...
r8320
return walkit(root, root == '')
Bryan O'Sullivan
Add inotify extension
r6239
Benoit Boissinot
inotify: remove unused variables
r8786 def _explain_watch_limit(ui, repo):
Bryan O'Sullivan
Add inotify extension
r6239 path = '/proc/sys/fs/inotify/max_user_watches'
try:
limit = int(file(path).read())
except IOError, err:
if err.errno != errno.ENOENT:
raise
raise util.Abort(_('this system does not seem to '
'support inotify'))
ui.warn(_('*** the current per-user limit on the number '
'of inotify watches is %s\n') % limit)
ui.warn(_('*** this limit is too low to watch every '
'directory in this repository\n'))
ui.warn(_('*** counting directories: '))
ndirs = len(list(walkrepodirs(repo)))
ui.warn(_('found %d\n') % ndirs)
newlimit = min(limit, 1024)
while newlimit < ((limit + ndirs) * 1.1):
newlimit *= 2
ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
(limit, newlimit))
ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
% repo.root)
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 class pollable(object):
"""
Interface to support polling.
The file descriptor returned by fileno() is registered to a polling
object.
Usage:
Every tick, check if an event has happened since the last tick:
* If yes, call handle_events
* If no, call handle_timeout
"""
Bryan O'Sullivan
Add inotify extension
r6239 poll_events = select.POLLIN
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 instances = {}
poll = select.poll()
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 def fileno(self):
raise NotImplementedError
def handle_events(self, events):
raise NotImplementedError
def handle_timeout(self):
raise NotImplementedError
def shutdown(self):
raise NotImplementedError
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 def register(self, timeout):
fd = self.fileno()
pollable.poll.register(fd, pollable.poll_events)
pollable.instances[fd] = self
self.registered = True
self.timeout = timeout
def unregister(self):
pollable.poll.unregister(self)
self.registered = False
Nicolas Dumazet
inotify: put the "while True: poll()" loop in pollable class
r8793 @classmethod
def run(cls):
while True:
timeout = None
timeobj = None
for obj in cls.instances.itervalues():
if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
timeout, timeobj = obj.timeout, obj
try:
events = cls.poll.poll(timeout)
except select.error, err:
if err[0] == errno.EINTR:
continue
raise
if events:
by_fd = {}
for fd, event in events:
by_fd.setdefault(fd, []).append(event)
for fd, events in by_fd.iteritems():
cls.instances[fd].handle_pollevents(events)
elif timeobj:
timeobj.handle_timeout()
Benoit Boissinot
inotify.server: the decorator eventaction() shouldn't be a method of repowatcher
r8791 def eventaction(code):
"""
Decorator to help handle events in repowatcher
"""
def decorator(f):
def wrapper(self, wpath):
if code == 'm' and wpath in self.lastevent and \
self.lastevent[wpath] in 'cm':
return
self.lastevent[wpath] = code
self.timeout = 250
f(self, wpath)
wrapper.func_name = f.func_name
return wrapper
return decorator
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 class repowatcher(pollable):
"""
Watches inotify events
"""
Bryan O'Sullivan
Add inotify extension
r6239 statuskeys = 'almr!?'
Nicolas Dumazet
inotify: make mask a class variable since it's instance-independant
r8383 mask = (
inotify.IN_ATTRIB |
inotify.IN_CREATE |
inotify.IN_DELETE |
inotify.IN_DELETE_SELF |
inotify.IN_MODIFY |
inotify.IN_MOVED_FROM |
inotify.IN_MOVED_TO |
inotify.IN_MOVE_SELF |
inotify.IN_ONLYDIR |
inotify.IN_UNMOUNT |
0)
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 def __init__(self, ui, repo):
Bryan O'Sullivan
Add inotify extension
r6239 self.ui = ui
self.repo = repo
self.wprefix = self.repo.wjoin('')
try:
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 self.watcher = watcher.watcher()
Bryan O'Sullivan
Add inotify extension
r6239 except OSError, err:
raise util.Abort(_('inotify service not available: %s') %
err.strerror)
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 self.threshold = watcher.threshold(self.watcher)
Bryan O'Sullivan
Add inotify extension
r6239 self.fileno = self.watcher.fileno
self.tree = {}
self.statcache = {}
self.statustrees = dict([(s, {}) for s in self.statuskeys])
self.last_event = None
Nicolas Dumazet
inotify: do not defer inotify events processing...
r8605 self.lastevent = {}
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 self.register(timeout=None)
Bryan O'Sullivan
Add inotify extension
r6239 self.ds_info = self.dirstate_info()
Nicolas Dumazet
inotify: do not recurse in handle_timeout(): call it explicitely, not in scan()...
r8604 self.handle_timeout()
Bryan O'Sullivan
Add inotify extension
r6239 self.scan()
def event_time(self):
last = self.last_event
now = time.time()
self.last_event = now
if last is None:
return 'start'
delta = now - last
if delta < 5:
return '+%.3f' % delta
if delta < 50:
return '+%.2f' % delta
return '+%.1f' % delta
def dirstate_info(self):
try:
st = os.lstat(self.repo.join('dirstate'))
return st.st_mtime, st.st_ino
except OSError, err:
if err.errno != errno.ENOENT:
raise
return 0, 0
def add_watch(self, path, mask):
if not path:
return
if self.watcher.path(path) is None:
if self.ui.debugflag:
self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
try:
self.watcher.add(path, mask)
except OSError, err:
if err.errno in (errno.ENOENT, errno.ENOTDIR):
return
if err.errno != errno.ENOSPC:
raise
Benoit Boissinot
inotify: remove unused variables
r8786 _explain_watch_limit(self.ui, self.repo)
Bryan O'Sullivan
Add inotify extension
r6239
def setup(self):
self.ui.note(_('watching directories under %r\n') % self.repo.root)
self.add_watch(self.repo.path, inotify.IN_DELETE)
self.check_dirstate()
def wpath(self, evt):
path = evt.fullpath
if path == self.repo.root:
return ''
if path.startswith(self.wprefix):
return path[len(self.wprefix):]
raise 'wtf? ' + path
def dir(self, tree, path):
if path:
for name in path.split('/'):
Nicolas Dumazet
inotify: dic.setdefault(k, d) ; v = dic[k] --> v = dic.setdefault(k, d)...
r8384 tree = tree.setdefault(name, {})
Bryan O'Sullivan
Add inotify extension
r6239 return tree
def lookup(self, path, tree):
if path:
try:
for name in path.split('/'):
tree = tree[name]
except KeyError:
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 return 'x'
Bryan O'Sullivan
Add inotify extension
r6239 except TypeError:
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 return 'd'
Bryan O'Sullivan
Add inotify extension
r6239 return tree
def filestatus(self, fn, st):
try:
type_, mode, size, time = self.repo.dirstate._map[fn][:4]
except KeyError:
type_ = '?'
if type_ == 'n':
st_mode, st_size, st_mtime = st
Matt Mackall
inotify: fix confusion on files in lookup state
r7082 if size == -1:
return 'l'
Bryan O'Sullivan
Add inotify extension
r6239 if size and (size != st_size or (mode ^ st_mode) & 0100):
return 'm'
if time != int(st_mtime):
return 'l'
return 'n'
if type_ == '?' and self.repo.dirstate._ignore(fn):
return 'i'
return type_
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 def updatefile(self, wfn, osstat):
'''
update the file entry of an existing file.
osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
'''
self._updatestatus(wfn, self.filestatus(wfn, osstat))
def deletefile(self, wfn, oldstatus):
'''
update the entry of a file which has been deleted.
oldstatus: char in statuskeys, status of the file before deletion
'''
if oldstatus == 'r':
newstatus = 'r'
elif oldstatus in 'almn':
newstatus = '!'
else:
newstatus = None
self.statcache.pop(wfn, None)
self._updatestatus(wfn, newstatus)
def _updatestatus(self, wfn, newstatus):
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 '''
Update the stored status of a file or directory.
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 newstatus: - char in (statuskeys + 'ni'), new status to apply.
- or None, to stop tracking wfn
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 '''
Nicolas Dumazet
inotify: server: move split() out of server...
r8787 root, fn = split(wfn)
Bryan O'Sullivan
Add inotify extension
r6239 d = self.dir(self.tree, root)
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 oldstatus = d.get(fn)
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 # oldstatus can be either:
# - None : fn is new
# - a char in statuskeys: fn is a (tracked) file
# - a dict: fn is a directory
isdir = isinstance(oldstatus, dict)
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 if self.ui.debugflag and oldstatus != newstatus:
Bryan O'Sullivan
Add inotify extension
r6239 if isdir:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('status: %r dir(%d) -> %s\n') %
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 (wfn, len(oldstatus), newstatus))
Bryan O'Sullivan
Add inotify extension
r6239 else:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('status: %r %s -> %s\n') %
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 (wfn, oldstatus, newstatus))
Bryan O'Sullivan
Add inotify extension
r6239 if not isdir:
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 if oldstatus and oldstatus in self.statuskeys \
and oldstatus != newstatus:
del self.dir(self.statustrees[oldstatus], root)[fn]
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 if newstatus and newstatus != 'i':
d[fn] = newstatus
if newstatus in self.statuskeys:
dd = self.dir(self.statustrees[newstatus], root)
if oldstatus != newstatus or fn not in dd:
dd[fn] = newstatus
Bryan O'Sullivan
Add inotify extension
r6239 else:
d.pop(fn, None)
Nicolas Dumazet
Fixing issue1542, adding a relevant test...
r7892
Bryan O'Sullivan
Add inotify extension
r6239
def check_deleted(self, key):
# Files that had been deleted but were present in the dirstate
# may have vanished from the dirstate; we must clean them up.
nuke = []
for wfn, ignore in self.walk(key, self.statustrees[key]):
if wfn not in self.repo.dirstate:
nuke.append(wfn)
for wfn in nuke:
Nicolas Dumazet
inotify: server: move split() out of server...
r8787 root, fn = split(wfn)
Bryan O'Sullivan
Add inotify extension
r6239 del self.dir(self.statustrees[key], root)[fn]
del self.dir(self.tree, root)[fn]
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def scan(self, topdir=''):
ds = self.repo.dirstate._map.copy()
self.add_watch(join(self.repo.root, topdir), self.mask)
Nicolas Dumazet
inotify: inotify.server.walk() filetype is never used, do not yield it
r8334 for root, dirs, files in walk(self.repo, topdir):
Bryan O'Sullivan
Add inotify extension
r6239 for d in dirs:
self.add_watch(join(root, d), self.mask)
wroot = root[len(self.wprefix):]
Nicolas Dumazet
inotify: inotify.server.walk() filetype is never used, do not yield it
r8334 for fn in files:
Bryan O'Sullivan
Add inotify extension
r6239 wfn = join(wroot, fn)
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.updatefile(wfn, self.getstat(wfn))
Bryan O'Sullivan
Add inotify extension
r6239 ds.pop(wfn, None)
wtopdir = topdir
if wtopdir and wtopdir[-1] != '/':
wtopdir += '/'
for wfn, state in ds.iteritems():
if not wfn.startswith(wtopdir):
continue
Gerard Korsten
inotify: server raising an error when removing a file (issue1371)...
r7302 try:
st = self.stat(wfn)
except OSError:
status = state[0]
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.deletefile(wfn, status)
Bryan O'Sullivan
Add inotify extension
r6239 else:
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.updatefile(wfn, st)
Bryan O'Sullivan
Add inotify extension
r6239 self.check_deleted('!')
self.check_deleted('r')
def check_dirstate(self):
ds_info = self.dirstate_info()
if ds_info == self.ds_info:
return
self.ds_info = ds_info
if not self.ui.debugflag:
self.last_event = None
self.ui.note(_('%s dirstate reload\n') % self.event_time())
self.repo.dirstate.invalidate()
Nicolas Dumazet
inotify: do not recurse in handle_timeout(): call it explicitely, not in scan()...
r8604 self.handle_timeout()
Bryan O'Sullivan
Add inotify extension
r6239 self.scan()
self.ui.note(_('%s end dirstate reload\n') % self.event_time())
def walk(self, states, tree, prefix=''):
# This is the "inner loop" when talking to the client.
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 for name, val in tree.iteritems():
path = join(prefix, name)
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 try:
if val in states:
yield path, val
except TypeError:
for p in self.walk(states, val, path):
yield p
Bryan O'Sullivan
Add inotify extension
r6239
def update_hgignore(self):
# An update of the ignore file can potentially change the
# states of all unknown and ignored files.
# XXX If the user has other ignore files outside the repo, or
# changes their list of ignore files at run time, we'll
# potentially never see changes to them. We could get the
# client to report to us what ignore data they're using.
# But it's easier to do nothing than to open that can of
# worms.
Matt Mackall
inotify: fixup rebuilding ignore
r7085 if '_ignore' in self.repo.dirstate.__dict__:
delattr(self.repo.dirstate, '_ignore')
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('rescanning due to .hgignore change\n'))
Nicolas Dumazet
inotify: do not recurse in handle_timeout(): call it explicitely, not in scan()...
r8604 self.handle_timeout()
Bryan O'Sullivan
Add inotify extension
r6239 self.scan()
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def getstat(self, wpath):
try:
return self.statcache[wpath]
except KeyError:
try:
return self.stat(wpath)
except OSError, err:
if err.errno != errno.ENOENT:
raise
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def stat(self, wpath):
try:
st = os.lstat(join(self.wprefix, wpath))
ret = st.st_mode, st.st_size, st.st_mtime
self.statcache[wpath] = ret
return ret
Benoit Boissinot
remove unused variables
r7280 except OSError:
Bryan O'Sullivan
Add inotify extension
r6239 self.statcache.pop(wpath, None)
raise
Thomas Arendsen Hein
Spacing cleanup
r6287
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 @eventaction('c')
Bryan O'Sullivan
Add inotify extension
r6239 def created(self, wpath):
if wpath == '.hgignore':
self.update_hgignore()
try:
st = self.stat(wpath)
if stat.S_ISREG(st[0]):
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.updatefile(wpath, st)
Benoit Boissinot
remove unused variables
r7280 except OSError:
Bryan O'Sullivan
Add inotify extension
r6239 pass
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 @eventaction('m')
Bryan O'Sullivan
Add inotify extension
r6239 def modified(self, wpath):
if wpath == '.hgignore':
self.update_hgignore()
try:
st = self.stat(wpath)
if stat.S_ISREG(st[0]):
if self.repo.dirstate[wpath] in 'lmn':
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.updatefile(wpath, st)
Bryan O'Sullivan
Add inotify extension
r6239 except OSError:
pass
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 @eventaction('d')
Bryan O'Sullivan
Add inotify extension
r6239 def deleted(self, wpath):
if wpath == '.hgignore':
self.update_hgignore()
elif wpath.startswith('.hg/'):
if wpath == '.hg/wlock':
self.check_dirstate()
return
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.deletefile(wpath, self.repo.dirstate[wpath])
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def process_create(self, wpath, evt):
if self.ui.debugflag:
self.ui.note(_('%s event: created %s\n') %
(self.event_time(), wpath))
if evt.mask & inotify.IN_ISDIR:
self.scan(wpath)
else:
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 self.created(wpath)
Bryan O'Sullivan
Add inotify extension
r6239
def process_delete(self, wpath, evt):
if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s event: deleted %s\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time(), wpath))
if evt.mask & inotify.IN_ISDIR:
Nicolas Dumazet
inotify: proper fix for issue1542 (partially reverting 67e59a9886d5)...
r8600 tree = self.dir(self.tree, wpath).copy()
for wfn, ignore in self.walk('?', tree):
self.deletefile(join(wpath, wfn), '?')
Bryan O'Sullivan
Add inotify extension
r6239 self.scan(wpath)
Nicolas Dumazet
inotify: proper fix for issue1542 (partially reverting 67e59a9886d5)...
r8600 else:
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 self.deleted(wpath)
Bryan O'Sullivan
Add inotify extension
r6239
def process_modify(self, wpath, evt):
if self.ui.debugflag:
self.ui.note(_('%s event: modified %s\n') %
(self.event_time(), wpath))
if not (evt.mask & inotify.IN_ISDIR):
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 self.modified(wpath)
Bryan O'Sullivan
Add inotify extension
r6239
def process_unmount(self, evt):
self.ui.warn(_('filesystem containing %s was unmounted\n') %
evt.fullpath)
sys.exit(0)
Nicolas Dumazet
inotify: process all inotify events in one batch...
r8609 def handle_pollevents(self, events):
Bryan O'Sullivan
Add inotify extension
r6239 if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s readable: %d bytes\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time(), self.threshold.readable()))
if not self.threshold():
if self.registered:
if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s below threshold - unhooking\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time()))
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 self.unregister()
Bryan O'Sullivan
Add inotify extension
r6239 self.timeout = 250
else:
self.read_events()
def read_events(self, bufsize=None):
events = self.watcher.read(bufsize)
if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s reading %d events\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time(), len(events)))
for evt in events:
wpath = self.wpath(evt)
if evt.mask & inotify.IN_UNMOUNT:
self.process_unmount(wpath, evt)
elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
self.process_modify(wpath, evt)
elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
inotify.IN_MOVED_FROM):
self.process_delete(wpath, evt)
elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
self.process_create(wpath, evt)
Nicolas Dumazet
inotify: do not defer inotify events processing...
r8605 self.lastevent.clear()
Bryan O'Sullivan
Add inotify extension
r6239 def handle_timeout(self):
if not self.registered:
if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time(), self.threshold.readable()))
self.read_events(0)
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 self.register(timeout=None)
Bryan O'Sullivan
Add inotify extension
r6239
self.timeout = None
def shutdown(self):
self.watcher.close()
Nicolas Dumazet
inotify: introduce debuginotify, which lists which paths are under watch
r8555 def debug(self):
"""
Returns a sorted list of relatives paths currently watched,
for debugging purposes.
"""
return sorted(tuple[0][len(self.wprefix):] for tuple in self.watcher)
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 class server(pollable):
"""
Listens for client queries on unix socket inotify.sock
"""
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 def __init__(self, ui, repo, repowatcher, timeout):
Bryan O'Sullivan
Add inotify extension
r6239 self.ui = ui
self.repo = repo
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 self.repowatcher = repowatcher
Bryan O'Sullivan
Add inotify extension
r6239 self.sock = socket.socket(socket.AF_UNIX)
self.sockpath = self.repo.join('inotify.sock')
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 self.realsockpath = None
Bryan O'Sullivan
Add inotify extension
r6239 try:
self.sock.bind(self.sockpath)
except socket.error, err:
if err[0] == errno.EADDRINUSE:
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 raise AlreadyStartedException(_('could not start server: %s')
Bryan O'Sullivan
Add inotify extension
r6239 % err[1])
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 if err[0] == "AF_UNIX path too long":
tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
self.realsockpath = os.path.join(tempdir, "inotify.sock")
try:
self.sock.bind(self.realsockpath)
os.symlink(self.realsockpath, self.sockpath)
except (OSError, socket.error), inst:
try:
os.unlink(self.realsockpath)
except:
pass
os.rmdir(tempdir)
if inst.errno == errno.EEXIST:
raise AlreadyStartedException(_('could not start server: %s')
% inst.strerror)
raise
else:
raise
Bryan O'Sullivan
Add inotify extension
r6239 self.sock.listen(5)
self.fileno = self.sock.fileno
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 self.register(timeout=timeout)
Bryan O'Sullivan
Add inotify extension
r6239
def handle_timeout(self):
pass
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 def answer_stat_query(self, cs):
Bryan O'Sullivan
Add inotify extension
r6239 names = cs.read().split('\0')
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 states = names.pop()
self.ui.note(_('answering query for %r\n') % states)
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 if self.repowatcher.timeout:
Bryan O'Sullivan
Add inotify extension
r6239 # We got a query while a rescan is pending. Make sure we
# rescan before responding, or we could give back a wrong
# answer.
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 self.repowatcher.handle_timeout()
Bryan O'Sullivan
Add inotify extension
r6239
if not names:
def genresult(states, tree):
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 for fn, state in self.repowatcher.walk(states, tree):
Bryan O'Sullivan
Add inotify extension
r6239 yield fn
else:
def genresult(states, tree):
for fn in names:
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 l = self.repowatcher.lookup(fn, tree)
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 try:
if l in states:
yield fn
except TypeError:
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 for f, s in self.repowatcher.walk(states, l, fn):
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 yield f
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 return ['\0'.join(r) for r in [
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 genresult('l', self.repowatcher.statustrees['l']),
genresult('m', self.repowatcher.statustrees['m']),
genresult('a', self.repowatcher.statustrees['a']),
genresult('r', self.repowatcher.statustrees['r']),
genresult('!', self.repowatcher.statustrees['!']),
'?' in states
and genresult('?', self.repowatcher.statustrees['?'])
or [],
Bryan O'Sullivan
Add inotify extension
r6239 [],
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 'c' in states and genresult('n', self.repowatcher.tree) or [],
Bryan O'Sullivan
Add inotify extension
r6239 ]]
Nicolas Dumazet
inotify: introduce debuginotify, which lists which paths are under watch
r8555 def answer_dbug_query(self):
return ['\0'.join(self.repowatcher.debug())]
Nicolas Dumazet
inotify: process all inotify events in one batch...
r8609 def handle_pollevents(self, events):
for e in events:
self.handle_pollevent()
Nicolas Dumazet
inotify: rename handle_event to handle_pollevent to avoid confusion...
r8608 def handle_pollevent(self):
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 sock, addr = self.sock.accept()
cs = common.recvcs(sock)
version = ord(cs.read(1))
if version != common.version:
self.ui.warn(_('received query from incompatible client '
'version %d\n') % version)
return
type = cs.read(4)
if type == 'STAT':
results = self.answer_stat_query(cs)
Nicolas Dumazet
inotify: introduce debuginotify, which lists which paths are under watch
r8555 elif type == 'DBUG':
results = self.answer_dbug_query()
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 else:
self.ui.warn(_('unrecognized query type: %s\n') % type)
return
Bryan O'Sullivan
Add inotify extension
r6239 try:
try:
Nicolas Dumazet
inotify: Abstract the layer format and sizes to a inotify.common dictionary...
r8386 v = chr(common.version)
Nicolas Dumazet
inotify: change protocol so that different query types can be supported.
r8553 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
*map(len, results)))
Bryan O'Sullivan
Add inotify extension
r6239 sock.sendall(''.join(results))
finally:
sock.shutdown(socket.SHUT_WR)
except socket.error, err:
if err[0] != errno.EPIPE:
raise
def shutdown(self):
self.sock.close()
try:
os.unlink(self.sockpath)
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 if self.realsockpath:
os.unlink(self.realsockpath)
os.rmdir(os.path.dirname(self.realsockpath))
Bryan O'Sullivan
Add inotify extension
r6239 except OSError, err:
if err.errno != errno.ENOENT:
raise
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 class master(object):
Bryan O'Sullivan
Add inotify extension
r6239 def __init__(self, ui, repo, timeout=None):
self.ui = ui
self.repo = repo
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 self.repowatcher = repowatcher(ui, repo)
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 self.server = server(ui, repo, self.repowatcher, timeout)
Bryan O'Sullivan
Add inotify extension
r6239
def shutdown(self):
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 for obj in pollable.instances.itervalues():
Bryan O'Sullivan
Add inotify extension
r6239 obj.shutdown()
def run(self):
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 self.repowatcher.setup()
Bryan O'Sullivan
Add inotify extension
r6239 self.ui.note(_('finished setup\n'))
if os.getenv('TIME_STARTUP'):
sys.exit(0)
Nicolas Dumazet
inotify: put the "while True: poll()" loop in pollable class
r8793 pollable.run()
Bryan O'Sullivan
Add inotify extension
r6239
def start(ui, repo):
Brendan Cully
inotify: close most file descriptors when autostarting...
r7451 def closefds(ignore):
# (from python bug #1177468)
# close all inherited file descriptors
# Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
# a file descriptor is kept internally as os._urandomfd (created on demand
# the first time os.urandom() is called), and should not be closed
try:
os.urandom(4)
urandom_fd = getattr(os, '_urandomfd', None)
except AttributeError:
urandom_fd = None
ignore.append(urandom_fd)
for fd in range(3, 256):
if fd in ignore:
continue
try:
os.close(fd)
except OSError:
pass
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 m = master(ui, repo)
Bryan O'Sullivan
Add inotify extension
r6239 sys.stdout.flush()
sys.stderr.flush()
pid = os.fork()
if pid:
return pid
Nicolas Dumazet
inotify: refactor (un)register methods into pollable object...
r8792 closefds(pollable.instances.keys())
Bryan O'Sullivan
Add inotify extension
r6239 os.setsid()
fd = os.open('/dev/null', os.O_RDONLY)
os.dup2(fd, 0)
if fd > 0:
os.close(fd)
fd = os.open(ui.config('inotify', 'log', '/dev/null'),
os.O_RDWR | os.O_CREAT | os.O_TRUNC)
os.dup2(fd, 1)
os.dup2(fd, 2)
if fd > 2:
os.close(fd)
try:
m.run()
finally:
m.shutdown()
os._exit(0)