##// END OF EJS Templates
merge main and crew
merge main and crew

File last commit:

r19869:4210a05c default
r20621:5beb49fd merge default
Show More
server.py
465 lines | 14.1 KiB | text/x-python | PythonLexer
Nicolas Dumazet
inotify: create a common, OS-independent server entry point...
r9933 # server.py - common entry point for inotify status server
Bryan O'Sullivan
Add inotify extension
r6239 #
Nicolas Dumazet
inotify: create a common, OS-independent server entry point...
r9933 # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com>
Bryan O'Sullivan
Add inotify extension
r6239 #
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
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Bryan O'Sullivan
Add inotify extension
r6239
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 from mercurial.i18n import _
Bryan O'Sullivan
posix: move server side of unix domain sockets out of inotify...
r18097 from mercurial import cmdutil, posix, osutil, util
Bryan O'Sullivan
Add inotify extension
r6239 import common
Nicolas Dumazet
inotify: create a common, OS-independent server entry point...
r9933 import errno
import os
import socket
import stat
import struct
import sys
Bryan O'Sullivan
Add inotify extension
r6239
Matt Mackall
many, many trivial check-code fixups
r10282 class AlreadyStartedException(Exception):
pass
Benoit Boissinot
inotify/inserve: implement --timeout-idle option (issue885)...
r10494 class TimeoutException(Exception):
pass
Bryan O'Sullivan
Add inotify extension
r6239
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
Matt Mackall
many, many trivial check-code fixups
r10282 return path[:c], path[c + 1:]
Nicolas Dumazet
inotify: server: move split() out of server...
r8787
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 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: 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: create a common, OS-independent server entry point...
r9933 class repowatcher(object):
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 """
Watches inotify events
"""
Bryan O'Sullivan
Add inotify extension
r6239 statuskeys = 'almr!?'
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
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
Nicolas Dumazet
inotify: create a common, OS-independent server entry point...
r9933 self.ds_info = self.dirstate_info()
Bryan O'Sullivan
Add inotify extension
r6239 self.last_event = None
Nicolas Dumazet
inotify: create a common, OS-independent server entry point...
r9933 def handle_timeout(self):
pass
Bryan O'Sullivan
Add inotify extension
r6239
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 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'
Renato Cunha
inotify: check all components of filenames against hgignore (issue884)...
r11545 if type_ == '?' and self.dirstate._dirignore(fn):
# we must check not only if the file is ignored, but if any part
# of its path match an ignore pattern
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 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: create a common, OS-independent server entry point...
r9933 class socketlistener(object):
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 """
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 try:
Bryan O'Sullivan
posix: move server side of unix domain sockets out of inotify...
r18097 self.sock = posix.unixdomainserver(
lambda p: os.path.join(root, '.hg', p),
'inotify')
except (OSError, socket.error), err:
Bryan O'Sullivan
inotify: on Python < 2.6, socket.error lacks errno
r18107 if err.args[0] == errno.EADDRINUSE:
Bryan O'Sullivan
posix: move server side of unix domain sockets out of inotify...
r18097 raise AlreadyStartedException(_('cannot start: '
'socket is already bound'))
raise
Bryan O'Sullivan
Add inotify extension
r6239 self.fileno = self.sock.fileno
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: 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: create a common, OS-independent server entry point...
r9933 def accept_connection(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))
Brodie Rao
cleanup: replace naked excepts with more specific ones
r16688 except socket.error:
Simon Heimberg
inotify: return version to client even when not matching...
r8952 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:
Renato Cunha
removed exception args indexing (not supported by py3k)...
r11567 if err.args[0] != errno.EPIPE:
Bryan O'Sullivan
Add inotify extension
r6239 raise
Nikolaj Sjujskij
building: build inotify for sys.platform='linux*'...
r15151 if sys.platform.startswith('linux'):
Nicolas Dumazet
inotify: create a common, OS-independent server entry point...
r9933 import linuxserver as _server
else:
raise ImportError
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: create a common, OS-independent server entry point...
r9933 master = _server.master
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):
Benoit Boissinot
inotify/inserve: implement --timeout-idle option (issue885)...
r10494 timeout = opts.get('idle_timeout')
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 if timeout:
Benoit Boissinot
inotify/inserve: implement --timeout-idle option (issue885)...
r10494 timeout = float(timeout) * 60000
else:
timeout = None
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514
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:
Nicolas Dumazet
inotify: improve error messages...
r9900 raise util.Abort("inotify-server: %s" % inst)
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 def run(self):
try:
Benoit Boissinot
inotify/inserve: implement --timeout-idle option (issue885)...
r10494 try:
self.master.run()
except TimeoutException:
pass
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 finally:
self.master.shutdown()
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 if 'inserve' not in sys.argv:
Patrick Mezard
Find right hg command for detached process...
r10239 runargs = util.hgcmd() + ['inserve', '-R', root]
Nicolas Dumazet
inotify: add a inotify.pidfile configuration possibility...
r9897 else:
Patrick Mezard
Find right hg command for detached process...
r10239 runargs = util.hgcmd() + sys.argv[1:]
Nicolas Dumazet
inotify: add a inotify.pidfile configuration possibility...
r9897
pidfile = ui.config('inotify', 'pidfile')
Siddharth Agarwal
inotify: add pidfile to parent options...
r19866 opts.setdefault('pid_file', '')
if opts['daemon'] and pidfile is not None and not opts['pid_file']:
opts['pid_file'] = pidfile
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')
Nicolas Dumazet
run-tests: --inotify: use inotify.appendpid to append pids to $DAEMON_PIDS...
r10013
appendpid = ui.configbool('inotify', 'appendpid', False)
Greg Ward
inotify: show the exact command used to start the server
r11943 ui.debug('starting inotify server: %s\n' % ' '.join(runargs))
Nicolas Dumazet
inotify: use cmdutil.service instead of local daemonizing code
r9514 cmdutil.service(opts, initfn=service.init, runfn=service.run,
Nicolas Dumazet
run-tests: --inotify: use inotify.appendpid to append pids to $DAEMON_PIDS...
r10013 logfile=logfile, runargs=runargs, appendpid=appendpid)