##// END OF EJS Templates
Added signature for changeset 31ec469f9b55
Added signature for changeset 31ec469f9b55

File last commit:

r9854:95e1867f default
r9871:57949bfe default
Show More
server.py
859 lines | 27.1 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 _
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 from mercurial import cmdutil, 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)
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 def walkrepodirs(dirstate, absroot):
Bryan O'Sullivan
Add inotify extension
r6239 '''Iterate over all subdirectories of this repo.
Exclude the .hg directory, any nested repos, and ignored dirs.'''
def walkit(dirname, top):
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 fullpath = join(absroot, 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)
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 if dirstate._ignore(d):
Bryan O'Sullivan
Add inotify extension
r6239 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
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 def walk(dirstate, absroot, root):
Bryan O'Sullivan
Add inotify extension
r6239 '''Like os.walk, but only yields regular files.'''
# This function is critical to performance during startup.
def walkit(root, reporoot):
files, dirs = [], []
try:
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 fullpath = join(absroot, root)
Bryan O'Sullivan
Add inotify extension
r6239 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)
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 if dirstate._ignore(path):
Nicolas Dumazet
inotify: server.walk(): use yield instead of for...
r8381 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:
Nicolas Dumazet
inotify: fix issue1375, add a test....
r9116 if err.errno == errno.ENOTDIR:
# fullpath was a directory, but has since been replaced
# by a file.
yield fullpath, dirs, files
elif err.errno not in walk_ignored_errors:
Bryan O'Sullivan
Add inotify extension
r6239 raise
Nicolas Dumazet
inotify: inotify.server.walk() simplify algorithm...
r8320
return walkit(root, root == '')
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 def _explain_watch_limit(ui, dirstate, rootabs):
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: '))
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 ndirs = len(list(walkrepodirs(dirstate, rootabs)))
Bryan O'Sullivan
Add inotify extension
r6239 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')
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 % rootabs)
Bryan O'Sullivan
Add inotify extension
r6239
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: new data structure to keep track of changes....
r9115 class directory(object):
"""
Representing a directory
* path is the relative path from repo root to this directory
* files is a dict listing the files in this directory
- keys are file names
- values are file status
* dirs is a dict listing the subdirectories
- key are subdirectories names
- values are directory objects
"""
def __init__(self, relpath=''):
self.path = relpath
self.files = {}
self.dirs = {}
def dir(self, relpath):
"""
Returns the directory contained at the relative path relpath.
Creates the intermediate directories if necessary.
"""
if not relpath:
return self
l = relpath.split('/')
ret = self
while l:
next = l.pop(0)
try:
ret = ret.dirs[next]
except KeyError:
d = directory(join(ret.path, next))
ret.dirs[next] = d
ret = d
return ret
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 def walk(self, states, visited=None):
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 """
yield (filename, status) pairs for items in the trees
that have status in states.
filenames are relative to the repo root
"""
for file, st in self.files.iteritems():
if st in states:
yield join(self.path, file), st
for dir in self.dirs.itervalues():
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 if visited is not None:
visited.add(dir.path)
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 for e in dir.walk(states):
yield e
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 def lookup(self, states, path, visited):
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 """
yield root-relative filenames that match path, and whose
status are in states:
* if path is a file, yield path
* if path is a directory, yield directory files
* if path is not tracked, yield nothing
"""
if path[-1] == '/':
path = path[:-1]
paths = path.split('/')
# we need to check separately for last node
last = paths.pop()
tree = self
try:
for dir in paths:
tree = tree.dirs[dir]
except KeyError:
# path is not tracked
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 visited.add(tree.path)
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 return
try:
# if path is a directory, walk it
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 target = tree.dirs[last]
visited.add(target.path)
for file, st in target.walk(states, visited):
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 yield file
except KeyError:
try:
if tree.files[last] in states:
# path is a file
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 visited.add(tree.path)
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 yield path
except KeyError:
# path is not tracked
pass
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: server: use dirstate instead of repo
r9350 def __init__(self, ui, dirstate, root):
Bryan O'Sullivan
Add inotify extension
r6239 self.ui = ui
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 self.dirstate = dirstate
self.wprefix = join(root, '')
Nicolas Dumazet
inotify: server: use wprefix everywhere, introduce prefixlen...
r9349 self.prefixlen = len(self.wprefix)
Bryan O'Sullivan
Add inotify extension
r6239 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
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 self.tree = directory()
Bryan O'Sullivan
Add inotify extension
r6239 self.statcache = {}
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 self.statustrees = dict([(s, directory()) for s in self.statuskeys])
Bryan O'Sullivan
Add inotify extension
r6239
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:
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 st = os.lstat(self.wprefix + '.hg/dirstate')
Bryan O'Sullivan
Add inotify extension
r6239 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:
Nicolas Dumazet
inotify: server: use wprefix everywhere, introduce prefixlen...
r9349 self.ui.note(_('watching %r\n') % path[self.prefixlen:])
Bryan O'Sullivan
Add inotify extension
r6239 try:
self.watcher.add(path, mask)
except OSError, err:
if err.errno in (errno.ENOENT, errno.ENOTDIR):
return
if err.errno != errno.ENOSPC:
raise
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 _explain_watch_limit(self.ui, self.dirstate, self.wprefix)
Bryan O'Sullivan
Add inotify extension
r6239
def setup(self):
Nicolas Dumazet
inotify: server: use wprefix everywhere, introduce prefixlen...
r9349 self.ui.note(_('watching directories under %r\n') % self.wprefix)
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 self.add_watch(self.wprefix + '.hg', inotify.IN_DELETE)
Bryan O'Sullivan
Add inotify extension
r6239 self.check_dirstate()
def filestatus(self, fn, st):
try:
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 type_, mode, size, time = self.dirstate._map[fn][:4]
Bryan O'Sullivan
Add inotify extension
r6239 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'
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 if type_ == '?' and self.dirstate._ignore(fn):
Bryan O'Sullivan
Add inotify extension
r6239 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 '''
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 Update the stored status of a file.
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382
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)
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 d = self.tree.dir(root)
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 oldstatus = d.files.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
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 if self.ui.debugflag and oldstatus != newstatus:
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 self.ui.note(_('status: %r %s -> %s\n') %
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 (wfn, oldstatus, newstatus))
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115
if oldstatus and oldstatus in self.statuskeys \
and oldstatus != newstatus:
del self.statustrees[oldstatus].dir(root).files[fn]
Nicolas Dumazet
inotify: server._updatestatus: simplify control flow
r9348
if newstatus in (None, 'i'):
d.files.pop(fn, None)
elif oldstatus != newstatus:
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 d.files[fn] = newstatus
Nicolas Dumazet
inotify: server._updatestatus: simplify control flow
r9348 if newstatus != 'n':
self.statustrees[newstatus].dir(root).files[fn] = newstatus
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 = []
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 for wfn, ignore in self.statustrees[key].walk(key):
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 if wfn not in self.dirstate:
Bryan O'Sullivan
Add inotify extension
r6239 nuke.append(wfn)
for wfn in nuke:
Nicolas Dumazet
inotify: server: move split() out of server...
r8787 root, fn = split(wfn)
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 del self.statustrees[key].dir(root).files[fn]
del self.tree.dir(root).files[fn]
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def scan(self, topdir=''):
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 ds = self.dirstate._map.copy()
Nicolas Dumazet
inotify: server: use wprefix everywhere, introduce prefixlen...
r9349 self.add_watch(join(self.wprefix, topdir), self.mask)
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 for root, dirs, files in walk(self.dirstate, self.wprefix, topdir):
Bryan O'Sullivan
Add inotify extension
r6239 for d in dirs:
self.add_watch(join(root, d), self.mask)
Nicolas Dumazet
inotify: server: use wprefix everywhere, introduce prefixlen...
r9349 wroot = root[self.prefixlen:]
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())
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 self.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 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.
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 if '_ignore' in self.dirstate.__dict__:
delattr(self.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]):
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 if self.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: use dirstate instead of repo
r9350 self.deletefile(wpath, self.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: server: new data structure to keep track of changes....
r9115 tree = self.tree.dir(wpath)
todelete = [wfn for wfn, ignore in tree.walk('?')]
for fn in todelete:
self.deletefile(fn, '?')
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:
Nicolas Dumazet
inotify: server: remove wpath method...
r8953 assert evt.fullpath.startswith(self.wprefix)
Nicolas Dumazet
inotify: server: use wprefix everywhere, introduce prefixlen...
r9349 wpath = evt.fullpath[self.prefixlen:]
Nicolas Dumazet
inotify: server: remove wpath method...
r8953
Nicolas Dumazet
inotify: server: explicitely ignore events in subdirs of .hg/ (issue1735)
r9117 # paths have been normalized, wpath never ends with a '/'
if wpath.startswith('.hg/') and evt.mask & inotify.IN_ISDIR:
# ignore subdirectories of .hg/ (merge, patches...)
continue
Bryan O'Sullivan
Add inotify extension
r6239 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.
"""
Nicolas Dumazet
inotify: server: use wprefix everywhere, introduce prefixlen...
r9349 return sorted(tuple[0][self.prefixlen:] for tuple in self.watcher)
Nicolas Dumazet
inotify: introduce debuginotify, which lists which paths are under watch
r8555
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: server: use dirstate instead of repo
r9350 def __init__(self, ui, root, repowatcher, timeout):
Bryan O'Sullivan
Add inotify extension
r6239 self.ui = ui
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)
Nicolas Dumazet
inotify: server: use dirstate instead of repo
r9350 self.sockpath = join(root, '.hg/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
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 visited = set()
Bryan O'Sullivan
Add inotify extension
r6239 if not names:
def genresult(states, tree):
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 for fn, state in tree.walk(states):
Bryan O'Sullivan
Add inotify extension
r6239 yield fn
else:
def genresult(states, tree):
for fn in names:
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 for f in tree.lookup(states, fn, visited):
Nicolas Dumazet
inotify: server: new data structure to keep track of changes....
r9115 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 [],
Nicolas Dumazet
inotify: mark directories visited during lookup (issue1844)...
r9854 visited
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)
Simon Heimberg
inotify: return version to client even when not matching...
r8952 try:
# try to send back our version to the client
# this way, the client too is informed of the mismatch
sock.sendall(chr(common.version))
except:
pass
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 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):
Nicolas Dumazet
inotify: client: no repo use
r9351 def __init__(self, ui, dirstate, root, timeout=None):
Bryan O'Sullivan
Add inotify extension
r6239 self.ui = ui
Nicolas Dumazet
inotify: client: no repo use
r9351 self.repowatcher = repowatcher(ui, dirstate, root)
self.server = server(ui, root, 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
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 def start(ui, dirstate, root, opts):
timeout = opts.get('timeout')
if timeout:
timeout = float(timeout) * 1e3
class service(object):
def init(self):
Brendan Cully
inotify: close most file descriptors when autostarting...
r7451 try:
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 self.master = master(ui, dirstate, root, timeout)
except AlreadyStartedException, inst:
raise util.Abort(str(inst))
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 def run(self):
try:
self.master.run()
finally:
self.master.shutdown()
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 runargs = None
if 'inserve' not in sys.argv:
runargs = [sys.argv[0], 'inserve', '-R', root]
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 service = service()
logfile = ui.config('inotify', 'log')
cmdutil.service(opts, initfn=service.init, runfn=service.run,
logfile=logfile, runargs=runargs)