diff --git a/hgext/inotify/__init__.py b/hgext/inotify/__init__.py deleted file mode 100644 --- a/hgext/inotify/__init__.py +++ /dev/null @@ -1,93 +0,0 @@ -# __init__.py - inotify-based status acceleration for Linux -# -# Copyright 2006, 2007, 2008 Bryan O'Sullivan -# Copyright 2007, 2008 Brendan Cully -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -'''accelerate status report using Linux's inotify service''' - -# todo: socket permissions - -from mercurial.i18n import _ -from mercurial import util -import server -from client import client, QueryFailed - -testedwith = 'internal' - -def serve(ui, repo, **opts): - '''start an inotify server for this repository''' - server.start(ui, repo.dirstate, repo.root, opts) - -def debuginotify(ui, repo, **opts): - '''debugging information for inotify extension - - Prints the list of directories being watched by the inotify server. - ''' - cli = client(ui, repo) - response = cli.debugquery() - - ui.write(_('directories being watched:\n')) - for path in response: - ui.write((' %s/\n') % path) - -def reposetup(ui, repo): - if not util.safehasattr(repo, 'dirstate'): - return - - class inotifydirstate(repo.dirstate.__class__): - - # We'll set this to false after an unsuccessful attempt so that - # next calls of status() within the same instance don't try again - # to start an inotify server if it won't start. - _inotifyon = True - - def status(self, match, subrepos, ignored, clean, unknown): - files = match.files() - if '.' in files: - files = [] - if (self._inotifyon and not ignored and not subrepos and - not self._dirty): - cli = client(ui, repo) - try: - result = cli.statusquery(files, match, False, - clean, unknown) - except QueryFailed, instr: - ui.debug(str(instr)) - # don't retry within the same hg instance - inotifydirstate._inotifyon = False - pass - else: - if ui.config('inotify', 'debug'): - r2 = super(inotifydirstate, self).status( - match, [], False, clean, unknown) - for c, a, b in zip('LMARDUIC', result, r2): - for f in a: - if f not in b: - ui.warn('*** inotify: %s +%s\n' % (c, f)) - for f in b: - if f not in a: - ui.warn('*** inotify: %s -%s\n' % (c, f)) - result = r2 - return result - return super(inotifydirstate, self).status( - match, subrepos, ignored, clean, unknown) - - repo.dirstate.__class__ = inotifydirstate - -cmdtable = { - 'debuginotify': - (debuginotify, [], ('hg debuginotify')), - '^inserve': - (serve, - [('d', 'daemon', None, _('run server in background')), - ('', 'daemon-pipefds', '', - _('used internally by daemon mode'), _('NUM')), - ('t', 'idle-timeout', '', - _('minutes to sit idle before exiting'), _('NUM')), - ('', 'pid-file', '', - _('name of file to write process ID to'), _('FILE'))], - _('hg inserve [OPTION]...')), - } diff --git a/hgext/inotify/client.py b/hgext/inotify/client.py deleted file mode 100644 --- a/hgext/inotify/client.py +++ /dev/null @@ -1,173 +0,0 @@ -# client.py - inotify status client -# -# Copyright 2006, 2007, 2008 Bryan O'Sullivan -# Copyright 2007, 2008 Brendan Cully -# Copyright 2009 Nicolas Dumazet -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -from mercurial.i18n import _ -import common, server -import errno, os, socket, struct - -class QueryFailed(Exception): - pass - -def start_server(function): - """ - Decorator. - Tries to call function, if it fails, try to (re)start inotify server. - Raise QueryFailed if something went wrong - """ - def decorated_function(self, *args): - try: - return function(self, *args) - except (OSError, socket.error), err: - autostart = self.ui.configbool('inotify', 'autostart', True) - - if err.args[0] == errno.ECONNREFUSED: - self.ui.warn(_('inotify-client: found dead inotify server ' - 'socket; removing it\n')) - os.unlink(os.path.join(self.root, '.hg', 'inotify.sock')) - if err.args[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart: - try: - try: - server.start(self.ui, self.dirstate, self.root, - dict(daemon=True, daemon_pipefds='')) - except server.AlreadyStartedException, inst: - # another process may have started its own - # inotify server while this one was starting. - self.ui.debug(str(inst)) - except Exception, inst: - self.ui.warn(_('inotify-client: could not start inotify ' - 'server: %s\n') % inst) - else: - try: - return function(self, *args) - except socket.error, err: - self.ui.warn(_('inotify-client: could not talk to new ' - 'inotify server: %s\n') % err.args[-1]) - elif err.args[0] in (errno.ECONNREFUSED, errno.ENOENT): - # silently ignore normal errors if autostart is False - self.ui.debug('(inotify server not running)\n') - else: - self.ui.warn(_('inotify-client: failed to contact inotify ' - 'server: %s\n') % err.args[-1]) - - self.ui.traceback() - raise QueryFailed('inotify query failed') - - return decorated_function - - -class client(object): - def __init__(self, ui, repo): - self.ui = ui - self.dirstate = repo.dirstate - self.root = repo.root - self.sock = socket.socket(socket.AF_UNIX) - - def _connect(self): - sockpath = os.path.join(self.root, '.hg', 'inotify.sock') - try: - self.sock.connect(sockpath) - except socket.error, err: - if err.args[0] == "AF_UNIX path too long": - sockpath = os.readlink(sockpath) - self.sock.connect(sockpath) - else: - raise - - def _send(self, type, data): - """Sends protocol version number, and the data""" - self.sock.sendall(chr(common.version) + type + data) - - self.sock.shutdown(socket.SHUT_WR) - - def _receive(self, type): - """ - Read data, check version number, extract headers, - and returns a tuple (data descriptor, header) - Raises QueryFailed on error - """ - cs = common.recvcs(self.sock) - try: - version = ord(cs.read(1)) - except TypeError: - # empty answer, assume the server crashed - self.ui.warn(_('inotify-client: received empty answer from inotify ' - 'server')) - raise QueryFailed('server crashed') - - if version != common.version: - self.ui.warn(_('(inotify: received response from incompatible ' - 'server version %d)\n') % version) - raise QueryFailed('incompatible server version') - - readtype = cs.read(4) - if readtype != type: - self.ui.warn(_('(inotify: received \'%s\' response when expecting' - ' \'%s\')\n') % (readtype, type)) - raise QueryFailed('wrong response type') - - hdrfmt = common.resphdrfmts[type] - hdrsize = common.resphdrsizes[type] - try: - resphdr = struct.unpack(hdrfmt, cs.read(hdrsize)) - except struct.error: - raise QueryFailed('unable to retrieve query response headers') - - return cs, resphdr - - def query(self, type, req): - self._connect() - - self._send(type, req) - - return self._receive(type) - - @start_server - def statusquery(self, names, match, ignored, clean, unknown=True): - - def genquery(): - for n in names: - yield n - states = 'almrx!' - if ignored: - raise ValueError('this is insanity') - if clean: - states += 'c' - if unknown: - states += '?' - yield states - - req = '\0'.join(genquery()) - - cs, resphdr = self.query('STAT', req) - - def readnames(nbytes): - if nbytes: - names = cs.read(nbytes) - if names: - return filter(match, names.split('\0')) - return [] - results = tuple(map(readnames, resphdr[:-1])) - - if names: - nbytes = resphdr[-1] - vdirs = cs.read(nbytes) - if vdirs: - for vdir in vdirs.split('\0'): - if match.explicitdir: - match.explicitdir(vdir) - - return results - - @start_server - def debugquery(self): - cs, resphdr = self.query('DBUG', '') - - nbytes = resphdr[0] - names = cs.read(nbytes) - return names.split('\0') diff --git a/hgext/inotify/common.py b/hgext/inotify/common.py deleted file mode 100644 --- a/hgext/inotify/common.py +++ /dev/null @@ -1,53 +0,0 @@ -# server.py - inotify common protocol code -# -# Copyright 2006, 2007, 2008 Bryan O'Sullivan -# Copyright 2007, 2008 Brendan Cully -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -import cStringIO, socket, struct - -""" - Protocol between inotify clients and server: - - Client sending query: - 1) send protocol version number - 2) send query type (string, 4 letters long) - 3) send query parameters: - - For STAT, N+1 \0-separated strings: - 1) N different names that need checking - 2) 1 string containing all the status types to match - - No parameter needed for DBUG - - Server sending query answer: - 1) send protocol version number - 2) send query type - 3) send struct.pack'ed headers describing the length of the content: - e.g. for STAT, receive 9 integers describing the length of the - 9 \0-separated string lists to be read: - * one file list for each lmar!?ic status type - * one list containing the directories visited during lookup - -""" - -version = 3 - -resphdrfmts = { - 'STAT': '>lllllllll', # status requests - 'DBUG': '>l' # debugging queries -} -resphdrsizes = dict((k, struct.calcsize(v)) - for k, v in resphdrfmts.iteritems()) - -def recvcs(sock): - cs = cStringIO.StringIO() - s = True - try: - while s: - s = sock.recv(65536) - cs.write(s) - finally: - sock.shutdown(socket.SHUT_RD) - cs.seek(0) - return cs diff --git a/hgext/inotify/linux/__init__.py b/hgext/inotify/linux/__init__.py deleted file mode 100644 --- a/hgext/inotify/linux/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# __init__.py - low-level interfaces to the Linux inotify subsystem - -# Copyright 2006 Bryan O'Sullivan - -# This library is free software; you can redistribute it and/or modify -# it under the terms of version 2.1 of the GNU Lesser General Public -# License, or any later version. - -'''Low-level interface to the Linux inotify subsystem. - -The inotify subsystem provides an efficient mechanism for file status -monitoring and change notification. - -This package provides the low-level inotify system call interface and -associated constants and helper functions. - -For a higher-level interface that remains highly efficient, use the -inotify.watcher package.''' - -__author__ = "Bryan O'Sullivan " - -from _inotify import * - -procfs_path = '/proc/sys/fs/inotify' - -def _read_procfs_value(name): - def read_value(): - try: - fp = open(procfs_path + '/' + name) - r = int(fp.read()) - fp.close() - return r - except OSError: - return None - - read_value.__doc__ = '''Return the value of the %s setting from /proc. - - If inotify is not enabled on this system, return None.''' % name - - return read_value - -max_queued_events = _read_procfs_value('max_queued_events') -max_user_instances = _read_procfs_value('max_user_instances') -max_user_watches = _read_procfs_value('max_user_watches') diff --git a/hgext/inotify/linux/_inotify.c b/hgext/inotify/linux/_inotify.c deleted file mode 100644 --- a/hgext/inotify/linux/_inotify.c +++ /dev/null @@ -1,649 +0,0 @@ -/* - * _inotify.c - Python extension interfacing to the Linux inotify subsystem - * - * Copyright 2006 Bryan O'Sullivan - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of version 2.1 of the GNU Lesser General - * Public License or any later version. - */ - -#include -#include -#include -#include -#include -#include - -#include - -/* Variables used in the event string representation */ -static PyObject *join; -static PyObject *er_wm; -static PyObject *er_wmc; -static PyObject *er_wmn; -static PyObject *er_wmcn; - -static PyObject *init(PyObject *self, PyObject *args) -{ - PyObject *ret = NULL; - int fd = -1; - - if (!PyArg_ParseTuple(args, ":init")) - goto bail; - - Py_BEGIN_ALLOW_THREADS; - fd = inotify_init(); - Py_END_ALLOW_THREADS; - - if (fd == -1) { - PyErr_SetFromErrno(PyExc_OSError); - goto bail; - } - - ret = PyInt_FromLong(fd); - if (ret == NULL) - goto bail; - - goto done; - -bail: - if (fd != -1) - close(fd); - - Py_CLEAR(ret); - -done: - return ret; -} - -PyDoc_STRVAR( - init_doc, - "init() -> fd\n" - "\n" - "Initialize an inotify instance.\n" - "Return a file descriptor associated with a new inotify event queue."); - -static PyObject *add_watch(PyObject *self, PyObject *args) -{ - PyObject *ret = NULL; - uint32_t mask; - int wd = -1; - char *path; - int fd; - - if (!PyArg_ParseTuple(args, "isI:add_watch", &fd, &path, &mask)) - goto bail; - - Py_BEGIN_ALLOW_THREADS; - wd = inotify_add_watch(fd, path, mask); - Py_END_ALLOW_THREADS; - - if (wd == -1) { - PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); - goto bail; - } - - ret = PyInt_FromLong(wd); - if (ret == NULL) - goto bail; - - goto done; - -bail: - if (wd != -1) - inotify_rm_watch(fd, wd); - - Py_CLEAR(ret); - -done: - return ret; -} - -PyDoc_STRVAR( - add_watch_doc, - "add_watch(fd, path, mask) -> wd\n" - "\n" - "Add a watch to an inotify instance, or modify an existing watch.\n" - "\n" - " fd: file descriptor returned by init()\n" - " path: path to watch\n" - " mask: mask of events to watch for\n" - "\n" - "Return a unique numeric watch descriptor for the inotify instance\n" - "mapped by the file descriptor."); - -static PyObject *remove_watch(PyObject *self, PyObject *args) -{ - uint32_t wd; - int fd; - int r; - - if (!PyArg_ParseTuple(args, "iI:remove_watch", &fd, &wd)) - return NULL; - - Py_BEGIN_ALLOW_THREADS; - r = inotify_rm_watch(fd, wd); - Py_END_ALLOW_THREADS; - - if (r == -1) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -PyDoc_STRVAR( - remove_watch_doc, - "remove_watch(fd, wd)\n" - "\n" - " fd: file descriptor returned by init()\n" - " wd: watch descriptor returned by add_watch()\n" - "\n" - "Remove a watch associated with the watch descriptor wd from the\n" - "inotify instance associated with the file descriptor fd.\n" - "\n" - "Removing a watch causes an IN_IGNORED event to be generated for this\n" - "watch descriptor."); - -#define bit_name(x) {x, #x} - -static struct { - int bit; - const char *name; - PyObject *pyname; -} bit_names[] = { - bit_name(IN_ACCESS), - bit_name(IN_MODIFY), - bit_name(IN_ATTRIB), - bit_name(IN_CLOSE_WRITE), - bit_name(IN_CLOSE_NOWRITE), - bit_name(IN_OPEN), - bit_name(IN_MOVED_FROM), - bit_name(IN_MOVED_TO), - bit_name(IN_CREATE), - bit_name(IN_DELETE), - bit_name(IN_DELETE_SELF), - bit_name(IN_MOVE_SELF), - bit_name(IN_UNMOUNT), - bit_name(IN_Q_OVERFLOW), - bit_name(IN_IGNORED), - bit_name(IN_ONLYDIR), - bit_name(IN_DONT_FOLLOW), - bit_name(IN_MASK_ADD), - bit_name(IN_ISDIR), - bit_name(IN_ONESHOT), - {0} -}; - -static PyObject *decode_mask(int mask) -{ - PyObject *ret = PyList_New(0); - int i; - - if (ret == NULL) - goto bail; - - for (i = 0; bit_names[i].bit; i++) { - if (mask & bit_names[i].bit) { - if (bit_names[i].pyname == NULL) { - bit_names[i].pyname = PyString_FromString(bit_names[i].name); - if (bit_names[i].pyname == NULL) - goto bail; - } - Py_INCREF(bit_names[i].pyname); - if (PyList_Append(ret, bit_names[i].pyname) == -1) - goto bail; - } - } - - goto done; - -bail: - Py_CLEAR(ret); - -done: - return ret; -} - -static PyObject *pydecode_mask(PyObject *self, PyObject *args) -{ - int mask; - - if (!PyArg_ParseTuple(args, "i:decode_mask", &mask)) - return NULL; - - return decode_mask(mask); -} - -PyDoc_STRVAR( - decode_mask_doc, - "decode_mask(mask) -> list_of_strings\n" - "\n" - "Decode an inotify mask value into a list of strings that give the\n" - "name of each bit set in the mask."); - -static char doc[] = "Low-level inotify interface wrappers."; - -static void define_const(PyObject *dict, const char *name, uint32_t val) -{ - PyObject *pyval = PyInt_FromLong(val); - PyObject *pyname = PyString_FromString(name); - - if (!pyname || !pyval) - goto bail; - - PyDict_SetItem(dict, pyname, pyval); - -bail: - Py_XDECREF(pyname); - Py_XDECREF(pyval); -} - -static void define_consts(PyObject *dict) -{ - define_const(dict, "IN_ACCESS", IN_ACCESS); - define_const(dict, "IN_MODIFY", IN_MODIFY); - define_const(dict, "IN_ATTRIB", IN_ATTRIB); - define_const(dict, "IN_CLOSE_WRITE", IN_CLOSE_WRITE); - define_const(dict, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE); - define_const(dict, "IN_OPEN", IN_OPEN); - define_const(dict, "IN_MOVED_FROM", IN_MOVED_FROM); - define_const(dict, "IN_MOVED_TO", IN_MOVED_TO); - - define_const(dict, "IN_CLOSE", IN_CLOSE); - define_const(dict, "IN_MOVE", IN_MOVE); - - define_const(dict, "IN_CREATE", IN_CREATE); - define_const(dict, "IN_DELETE", IN_DELETE); - define_const(dict, "IN_DELETE_SELF", IN_DELETE_SELF); - define_const(dict, "IN_MOVE_SELF", IN_MOVE_SELF); - define_const(dict, "IN_UNMOUNT", IN_UNMOUNT); - define_const(dict, "IN_Q_OVERFLOW", IN_Q_OVERFLOW); - define_const(dict, "IN_IGNORED", IN_IGNORED); - - define_const(dict, "IN_ONLYDIR", IN_ONLYDIR); - define_const(dict, "IN_DONT_FOLLOW", IN_DONT_FOLLOW); - define_const(dict, "IN_MASK_ADD", IN_MASK_ADD); - define_const(dict, "IN_ISDIR", IN_ISDIR); - define_const(dict, "IN_ONESHOT", IN_ONESHOT); - define_const(dict, "IN_ALL_EVENTS", IN_ALL_EVENTS); -} - -struct event { - PyObject_HEAD - PyObject *wd; - PyObject *mask; - PyObject *cookie; - PyObject *name; -}; - -static PyObject *event_wd(PyObject *self, void *x) -{ - struct event *evt = (struct event *)self; - Py_INCREF(evt->wd); - return evt->wd; -} - -static PyObject *event_mask(PyObject *self, void *x) -{ - struct event *evt = (struct event *)self; - Py_INCREF(evt->mask); - return evt->mask; -} - -static PyObject *event_cookie(PyObject *self, void *x) -{ - struct event *evt = (struct event *)self; - Py_INCREF(evt->cookie); - return evt->cookie; -} - -static PyObject *event_name(PyObject *self, void *x) -{ - struct event *evt = (struct event *)self; - Py_INCREF(evt->name); - return evt->name; -} - -static struct PyGetSetDef event_getsets[] = { - {"wd", event_wd, NULL, - "watch descriptor"}, - {"mask", event_mask, NULL, - "event mask"}, - {"cookie", event_cookie, NULL, - "rename cookie, if rename-related event"}, - {"name", event_name, NULL, - "file name"}, - {NULL} -}; - -PyDoc_STRVAR( - event_doc, - "event: Structure describing an inotify event."); - -static PyObject *event_new(PyTypeObject *t, PyObject *a, PyObject *k) -{ - return (*t->tp_alloc)(t, 0); -} - -static void event_dealloc(struct event *evt) -{ - Py_XDECREF(evt->wd); - Py_XDECREF(evt->mask); - Py_XDECREF(evt->cookie); - Py_XDECREF(evt->name); - - Py_TYPE(evt)->tp_free(evt); -} - -static PyObject *event_repr(struct event *evt) -{ - int cookie = evt->cookie == Py_None ? -1 : PyInt_AsLong(evt->cookie); - PyObject *ret = NULL, *pymasks = NULL, *pymask = NULL; - PyObject *tuple = NULL, *formatstr = NULL; - - pymasks = decode_mask(PyInt_AsLong(evt->mask)); - if (pymasks == NULL) - goto bail; - - pymask = _PyString_Join(join, pymasks); - if (pymask == NULL) - goto bail; - - if (evt->name != Py_None) { - if (cookie == -1) { - formatstr = er_wmn; - tuple = PyTuple_Pack(3, evt->wd, pymask, evt->name); - } - else { - formatstr = er_wmcn; - tuple = PyTuple_Pack(4, evt->wd, pymask, - evt->cookie, evt->name); - } - } else { - if (cookie == -1) { - formatstr = er_wm; - tuple = PyTuple_Pack(2, evt->wd, pymask); - } - else { - formatstr = er_wmc; - tuple = PyTuple_Pack(3, evt->wd, pymask, evt->cookie); - } - } - - if (tuple == NULL) - goto bail; - - ret = PyNumber_Remainder(formatstr, tuple); - - if (ret == NULL) - goto bail; - - goto done; -bail: - Py_CLEAR(ret); - -done: - Py_XDECREF(pymask); - Py_XDECREF(pymasks); - Py_XDECREF(tuple); - - return ret; -} - -static PyTypeObject event_type = { - PyVarObject_HEAD_INIT(NULL, 0) - "_inotify.event", /*tp_name*/ - sizeof(struct event), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor)event_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)event_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - event_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ - event_getsets, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - event_new, /* tp_new */ -}; - -PyObject *read_events(PyObject *self, PyObject *args) -{ - PyObject *ctor_args = NULL; - PyObject *pybufsize = NULL; - PyObject *ret = NULL; - int bufsize = 65536; - char *buf = NULL; - int nread, pos; - int fd; - - if (!PyArg_ParseTuple(args, "i|O:read", &fd, &pybufsize)) - goto bail; - - if (pybufsize && pybufsize != Py_None) - bufsize = PyInt_AsLong(pybufsize); - - ret = PyList_New(0); - if (ret == NULL) - goto bail; - - if (bufsize <= 0) { - int r; - - Py_BEGIN_ALLOW_THREADS; - r = ioctl(fd, FIONREAD, &bufsize); - Py_END_ALLOW_THREADS; - - if (r == -1) { - PyErr_SetFromErrno(PyExc_OSError); - goto bail; - } - if (bufsize == 0) - goto done; - } - else { - static long name_max; - static long name_fd = -1; - long min; - - if (name_fd != fd) { - name_fd = fd; - Py_BEGIN_ALLOW_THREADS; - name_max = fpathconf(fd, _PC_NAME_MAX); - Py_END_ALLOW_THREADS; - } - - min = sizeof(struct inotify_event) + name_max + 1; - - if (bufsize < min) { - PyErr_Format(PyExc_ValueError, - "bufsize must be at least %d", (int)min); - goto bail; - } - } - - buf = alloca(bufsize); - - Py_BEGIN_ALLOW_THREADS; - nread = read(fd, buf, bufsize); - Py_END_ALLOW_THREADS; - - if (nread == -1) { - PyErr_SetFromErrno(PyExc_OSError); - goto bail; - } - - ctor_args = PyTuple_New(0); - - if (ctor_args == NULL) - goto bail; - - pos = 0; - - while (pos < nread) { - struct inotify_event *in = (struct inotify_event *)(buf + pos); - struct event *evt; - PyObject *obj; - - obj = PyObject_CallObject((PyObject *)&event_type, ctor_args); - - if (obj == NULL) - goto bail; - - evt = (struct event *)obj; - - evt->wd = PyInt_FromLong(in->wd); - evt->mask = PyInt_FromLong(in->mask); - if (in->mask & IN_MOVE) - evt->cookie = PyInt_FromLong(in->cookie); - else { - Py_INCREF(Py_None); - evt->cookie = Py_None; - } - if (in->len) - evt->name = PyString_FromString(in->name); - else { - Py_INCREF(Py_None); - evt->name = Py_None; - } - - if (!evt->wd || !evt->mask || !evt->cookie || !evt->name) - goto mybail; - - if (PyList_Append(ret, obj) == -1) - goto mybail; - - pos += sizeof(struct inotify_event) + in->len; - continue; - - mybail: - Py_CLEAR(evt->wd); - Py_CLEAR(evt->mask); - Py_CLEAR(evt->cookie); - Py_CLEAR(evt->name); - Py_DECREF(obj); - - goto bail; - } - - goto done; - -bail: - Py_CLEAR(ret); - -done: - Py_XDECREF(ctor_args); - - return ret; -} - -static int init_globals(void) -{ - join = PyString_FromString("|"); - er_wm = PyString_FromString("event(wd=%d, mask=%s)"); - er_wmn = PyString_FromString("event(wd=%d, mask=%s, name=%s)"); - er_wmc = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x)"); - er_wmcn = PyString_FromString("event(wd=%d, mask=%s, cookie=0x%x, name=%s)"); - - return join && er_wm && er_wmn && er_wmc && er_wmcn; -} - -PyDoc_STRVAR( - read_doc, - "read(fd, bufsize[=65536]) -> list_of_events\n" - "\n" - "\nRead inotify events from a file descriptor.\n" - "\n" - " fd: file descriptor returned by init()\n" - " bufsize: size of buffer to read into, in bytes\n" - "\n" - "Return a list of event objects.\n" - "\n" - "If bufsize is > 0, block until events are available to be read.\n" - "Otherwise, immediately return all events that can be read without\n" - "blocking."); - -static PyMethodDef methods[] = { - {"init", init, METH_VARARGS, init_doc}, - {"add_watch", add_watch, METH_VARARGS, add_watch_doc}, - {"remove_watch", remove_watch, METH_VARARGS, remove_watch_doc}, - {"read", read_events, METH_VARARGS, read_doc}, - {"decode_mask", pydecode_mask, METH_VARARGS, decode_mask_doc}, - {NULL}, -}; - -#ifdef IS_PY3K -static struct PyModuleDef _inotify_module = { - PyModuleDef_HEAD_INIT, - "_inotify", - doc, - -1, - methods -}; - -PyMODINIT_FUNC PyInit__inotify(void) -{ - PyObject *mod, *dict; - - mod = PyModule_Create(&_inotify_module); - - if (mod == NULL) - return NULL; - - if (!init_globals()) - return; - - dict = PyModule_GetDict(mod); - - if (dict) - define_consts(dict); - - return mod; -} -#else -void init_inotify(void) -{ - PyObject *mod, *dict; - - if (PyType_Ready(&event_type) == -1) - return; - - if (!init_globals()) - return; - - mod = Py_InitModule3("_inotify", methods, doc); - - dict = PyModule_GetDict(mod); - - if (dict) - define_consts(dict); -} -#endif diff --git a/hgext/inotify/linux/watcher.py b/hgext/inotify/linux/watcher.py deleted file mode 100644 --- a/hgext/inotify/linux/watcher.py +++ /dev/null @@ -1,335 +0,0 @@ -# watcher.py - high-level interfaces to the Linux inotify subsystem - -# Copyright 2006 Bryan O'Sullivan - -# This library is free software; you can redistribute it and/or modify -# it under the terms of version 2.1 of the GNU Lesser General Public -# License, or any later version. - -'''High-level interfaces to the Linux inotify subsystem. - -The inotify subsystem provides an efficient mechanism for file status -monitoring and change notification. - -The watcher class hides the low-level details of the inotify -interface, and provides a Pythonic wrapper around it. It generates -events that provide somewhat more information than raw inotify makes -available. - -The autowatcher class is more useful, as it automatically watches -newly-created directories on your behalf.''' - -__author__ = "Bryan O'Sullivan " - -import _inotify as inotify -import array -import errno -import fcntl -import os -import termios - - -class event(object): - '''Derived inotify event class. - - The following fields are available: - - mask: event mask, indicating what kind of event this is - - cookie: rename cookie, if a rename-related event - - path: path of the directory in which the event occurred - - name: name of the directory entry to which the event occurred - (may be None if the event happened to a watched directory) - - fullpath: complete path at which the event occurred - - wd: watch descriptor that triggered this event''' - - __slots__ = ( - 'cookie', - 'fullpath', - 'mask', - 'name', - 'path', - 'raw', - 'wd', - ) - - def __init__(self, raw, path): - self.path = path - self.raw = raw - if raw.name: - self.fullpath = path + '/' + raw.name - else: - self.fullpath = path - - self.wd = raw.wd - self.mask = raw.mask - self.cookie = raw.cookie - self.name = raw.name - - def __repr__(self): - r = repr(self.raw) - return 'event(path=' + repr(self.path) + ', ' + r[r.find('(') + 1:] - - -_event_props = { - 'access': 'File was accessed', - 'modify': 'File was modified', - 'attrib': 'Attribute of a directory entry was changed', - 'close_write': 'File was closed after being written to', - 'close_nowrite': 'File was closed without being written to', - 'open': 'File was opened', - 'moved_from': 'Directory entry was renamed from this name', - 'moved_to': 'Directory entry was renamed to this name', - 'create': 'Directory entry was created', - 'delete': 'Directory entry was deleted', - 'delete_self': 'The watched directory entry was deleted', - 'move_self': 'The watched directory entry was renamed', - 'unmount': 'Directory was unmounted, and can no longer be watched', - 'q_overflow': 'Kernel dropped events due to queue overflow', - 'ignored': 'Directory entry is no longer being watched', - 'isdir': 'Event occurred on a directory', - } - -for k, v in _event_props.iteritems(): - mask = getattr(inotify, 'IN_' + k.upper()) - def getter(self): - return self.mask & mask - getter.__name__ = k - getter.__doc__ = v - setattr(event, k, property(getter, doc=v)) - -del _event_props - - -class watcher(object): - '''Provide a Pythonic interface to the low-level inotify API. - - Also adds derived information to each event that is not available - through the normal inotify API, such as directory name.''' - - __slots__ = ( - 'fd', - '_paths', - '_wds', - ) - - def __init__(self): - '''Create a new inotify instance.''' - - self.fd = inotify.init() - self._paths = {} - self._wds = {} - - def fileno(self): - '''Return the file descriptor this watcher uses. - - Useful for passing to select and poll.''' - - return self.fd - - def add(self, path, mask): - '''Add or modify a watch. - - Return the watch descriptor added or modified.''' - - path = os.path.normpath(path) - wd = inotify.add_watch(self.fd, path, mask) - self._paths[path] = wd, mask - self._wds[wd] = path, mask - return wd - - def remove(self, wd): - '''Remove the given watch.''' - - inotify.remove_watch(self.fd, wd) - self._remove(wd) - - def _remove(self, wd): - path_mask = self._wds.pop(wd, None) - if path_mask is not None: - self._paths.pop(path_mask[0]) - - def path(self, path): - '''Return a (watch descriptor, event mask) pair for the given path. - - If the path is not being watched, return None.''' - - return self._paths.get(path) - - def wd(self, wd): - '''Return a (path, event mask) pair for the given watch descriptor. - - If the watch descriptor is not valid or not associated with - this watcher, return None.''' - - return self._wds.get(wd) - - def read(self, bufsize=None): - '''Read a list of queued inotify events. - - If bufsize is zero, only return those events that can be read - immediately without blocking. Otherwise, block until events are - available.''' - - events = [] - for evt in inotify.read(self.fd, bufsize): - events.append(event(evt, self._wds[evt.wd][0])) - if evt.mask & inotify.IN_IGNORED: - self._remove(evt.wd) - elif evt.mask & inotify.IN_UNMOUNT: - self.close() - return events - - def close(self): - '''Shut down this watcher. - - All subsequent method calls are likely to raise exceptions.''' - - os.close(self.fd) - self.fd = None - self._paths = None - self._wds = None - - def __len__(self): - '''Return the number of active watches.''' - - return len(self._paths) - - def __iter__(self): - '''Yield a (path, watch descriptor, event mask) tuple for each - entry being watched.''' - - for path, (wd, mask) in self._paths.iteritems(): - yield path, wd, mask - - def __del__(self): - if self.fd is not None: - os.close(self.fd) - - ignored_errors = [errno.ENOENT, errno.EPERM, errno.ENOTDIR] - - def add_iter(self, path, mask, onerror=None): - '''Add or modify watches over path and its subdirectories. - - Yield each added or modified watch descriptor. - - To ensure that this method runs to completion, you must - iterate over all of its results, even if you do not care what - they are. For example: - - for wd in w.add_iter(path, mask): - pass - - By default, errors are ignored. If optional arg "onerror" is - specified, it should be a function; it will be called with one - argument, an OSError instance. It can report the error to - continue with the walk, or raise the exception to abort the - walk.''' - - # Add the IN_ONLYDIR flag to the event mask, to avoid a possible - # race when adding a subdirectory. In the time between the - # event being queued by the kernel and us processing it, the - # directory may have been deleted, or replaced with a different - # kind of entry with the same name. - - submask = mask | inotify.IN_ONLYDIR - - try: - yield self.add(path, mask) - except OSError, err: - if onerror and err.errno not in self.ignored_errors: - onerror(err) - for root, dirs, names in os.walk(path, topdown=False, onerror=onerror): - for d in dirs: - try: - yield self.add(root + '/' + d, submask) - except OSError, err: - if onerror and err.errno not in self.ignored_errors: - onerror(err) - - def add_all(self, path, mask, onerror=None): - '''Add or modify watches over path and its subdirectories. - - Return a list of added or modified watch descriptors. - - By default, errors are ignored. If optional arg "onerror" is - specified, it should be a function; it will be called with one - argument, an OSError instance. It can report the error to - continue with the walk, or raise the exception to abort the - walk.''' - - return [w for w in self.add_iter(path, mask, onerror)] - - -class autowatcher(watcher): - '''watcher class that automatically watches newly created directories.''' - - __slots__ = ( - 'addfilter', - ) - - def __init__(self, addfilter=None): - '''Create a new inotify instance. - - This instance will automatically watch newly created - directories. - - If the optional addfilter parameter is not None, it must be a - callable that takes one parameter. It will be called each time - a directory is about to be automatically watched. If it returns - True, the directory will be watched if it still exists, - otherwise, it will be skipped.''' - - super(autowatcher, self).__init__() - self.addfilter = addfilter - - _dir_create_mask = inotify.IN_ISDIR | inotify.IN_CREATE - - def read(self, bufsize=None): - events = super(autowatcher, self).read(bufsize) - for evt in events: - if evt.mask & self._dir_create_mask == self._dir_create_mask: - if self.addfilter is None or self.addfilter(evt): - parentmask = self._wds[evt.wd][1] - # See note about race avoidance via IN_ONLYDIR above. - mask = parentmask | inotify.IN_ONLYDIR - try: - self.add_all(evt.fullpath, mask) - except OSError, err: - if err.errno not in self.ignored_errors: - raise - return events - - -class threshold(object): - '''Class that indicates whether a file descriptor has reached a - threshold of readable bytes available. - - This class is not thread-safe.''' - - __slots__ = ( - 'fd', - 'threshold', - '_iocbuf', - ) - - def __init__(self, fd, threshold=1024): - self.fd = fd - self.threshold = threshold - self._iocbuf = array.array('i', [0]) - - def readable(self): - '''Return the number of bytes readable on this file descriptor.''' - - fcntl.ioctl(self.fd, termios.FIONREAD, self._iocbuf, True) - return self._iocbuf[0] - - def __call__(self): - '''Indicate whether the number of readable bytes has met or - exceeded the threshold.''' - - return self.readable() >= self.threshold diff --git a/hgext/inotify/linuxserver.py b/hgext/inotify/linuxserver.py deleted file mode 100644 --- a/hgext/inotify/linuxserver.py +++ /dev/null @@ -1,437 +0,0 @@ -# linuxserver.py - inotify status server for linux -# -# Copyright 2006, 2007, 2008 Bryan O'Sullivan -# Copyright 2007, 2008 Brendan Cully -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -from mercurial.i18n import _ -from mercurial import osutil, util, error -import server -import errno, os, select, stat, sys, time - -try: - import linux as inotify - from linux import watcher -except ImportError: - raise - -def walkrepodirs(dirstate, absroot): - '''Iterate over all subdirectories of this repo. - Exclude the .hg directory, any nested repos, and ignored dirs.''' - def walkit(dirname, top): - fullpath = server.join(absroot, dirname) - try: - for name, kind in osutil.listdir(fullpath): - if kind == stat.S_IFDIR: - if name == '.hg': - if not top: - return - else: - d = server.join(dirname, name) - if dirstate._ignore(d): - continue - for subdir in walkit(d, False): - yield subdir - except OSError, err: - if err.errno not in server.walk_ignored_errors: - raise - yield fullpath - - return walkit('', True) - -def _explain_watch_limit(ui, dirstate, rootabs): - path = '/proc/sys/fs/inotify/max_user_watches' - try: - limit = int(util.readfile(path)) - 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(dirstate, rootabs))) - 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') - % rootabs) - -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 - """ - poll_events = select.POLLIN - instances = {} - poll = select.poll() - - def fileno(self): - raise NotImplementedError - - def handle_events(self, events): - raise NotImplementedError - - def handle_timeout(self): - raise NotImplementedError - - def shutdown(self): - raise NotImplementedError - - 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 - - @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.args[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() - -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 - -class repowatcher(server.repowatcher, pollable): - """ - Watches inotify events - """ - 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) - - def __init__(self, ui, dirstate, root): - server.repowatcher.__init__(self, ui, dirstate, root) - - self.lastevent = {} - self.dirty = False - try: - self.watcher = watcher.watcher() - except OSError, err: - raise util.Abort(_('inotify service not available: %s') % - err.strerror) - self.threshold = watcher.threshold(self.watcher) - self.fileno = self.watcher.fileno - self.register(timeout=None) - - self.handle_timeout() - 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 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[self.prefixlen:]) - try: - self.watcher.add(path, mask) - except OSError, err: - if err.errno in (errno.ENOENT, errno.ENOTDIR): - return - if err.errno != errno.ENOSPC: - raise - _explain_watch_limit(self.ui, self.dirstate, self.wprefix) - - def setup(self): - self.ui.note(_('watching directories under %r\n') % self.wprefix) - self.add_watch(self.wprefix + '.hg', inotify.IN_DELETE) - - def scan(self, topdir=''): - ds = self.dirstate._map.copy() - self.add_watch(server.join(self.wprefix, topdir), self.mask) - for root, dirs, files in server.walk(self.dirstate, self.wprefix, - topdir): - for d in dirs: - self.add_watch(server.join(root, d), self.mask) - wroot = root[self.prefixlen:] - for fn in files: - wfn = server.join(wroot, fn) - self.updatefile(wfn, self.getstat(wfn)) - ds.pop(wfn, None) - wtopdir = topdir - if wtopdir and wtopdir[-1] != '/': - wtopdir += '/' - for wfn, state in ds.iteritems(): - if not wfn.startswith(wtopdir): - continue - try: - st = self.stat(wfn) - except OSError: - status = state[0] - self.deletefile(wfn, status) - else: - self.updatefile(wfn, st) - self.check_deleted('!') - self.check_deleted('r') - - @eventaction('c') - def created(self, wpath): - if wpath == '.hgignore': - self.update_hgignore() - try: - st = self.stat(wpath) - if stat.S_ISREG(st[0]) or stat.S_ISLNK(st[0]): - self.updatefile(wpath, st) - except OSError: - pass - - @eventaction('m') - def modified(self, wpath): - if wpath == '.hgignore': - self.update_hgignore() - try: - st = self.stat(wpath) - if stat.S_ISREG(st[0]): - if self.dirstate[wpath] in 'lmn': - self.updatefile(wpath, st) - except OSError: - pass - - @eventaction('d') - def deleted(self, wpath): - if wpath == '.hgignore': - self.update_hgignore() - elif wpath.startswith('.hg/'): - return - - self.deletefile(wpath, self.dirstate[wpath]) - - 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: - self.created(wpath) - - def process_delete(self, wpath, evt): - if self.ui.debugflag: - self.ui.note(_('%s event: deleted %s\n') % - (self.event_time(), wpath)) - - if evt.mask & inotify.IN_ISDIR: - tree = self.tree.dir(wpath) - todelete = [wfn for wfn, ignore in tree.walk('?')] - for fn in todelete: - self.deletefile(fn, '?') - self.scan(wpath) - else: - self.deleted(wpath) - - 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): - self.modified(wpath) - - def process_unmount(self, evt): - self.ui.warn(_('filesystem containing %s was unmounted\n') % - evt.fullpath) - sys.exit(0) - - def handle_pollevents(self, events): - if self.ui.debugflag: - self.ui.note(_('%s readable: %d bytes\n') % - (self.event_time(), self.threshold.readable())) - if not self.threshold(): - if self.registered: - if self.ui.debugflag: - self.ui.note(_('%s below threshold - unhooking\n') % - (self.event_time())) - self.unregister() - self.timeout = 250 - else: - self.read_events() - - def read_events(self, bufsize=None): - events = self.watcher.read(bufsize) - if self.ui.debugflag: - self.ui.note(_('%s reading %d events\n') % - (self.event_time(), len(events))) - for evt in events: - if evt.fullpath == self.wprefix[:-1]: - # events on the root of the repository - # itself, e.g. permission changes or repository move - continue - assert evt.fullpath.startswith(self.wprefix) - wpath = evt.fullpath[self.prefixlen:] - - # 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 - if wpath == ".hg/wlock": - if evt.mask & inotify.IN_DELETE: - self.dirstate.invalidate() - self.dirty = False - self.scan() - elif evt.mask & inotify.IN_CREATE: - self.dirty = True - else: - if self.dirty: - continue - - 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) - - self.lastevent.clear() - - def handle_timeout(self): - if not self.registered: - if self.ui.debugflag: - self.ui.note(_('%s hooking back up with %d bytes readable\n') % - (self.event_time(), self.threshold.readable())) - self.read_events(0) - self.register(timeout=None) - - self.timeout = None - - def shutdown(self): - self.watcher.close() - - def debug(self): - """ - Returns a sorted list of relatives paths currently watched, - for debugging purposes. - """ - return sorted(tuple[0][self.prefixlen:] for tuple in self.watcher) - -class socketlistener(server.socketlistener, pollable): - """ - Listens for client queries on unix socket inotify.sock - """ - def __init__(self, ui, root, repowatcher, timeout): - server.socketlistener.__init__(self, ui, root, repowatcher, timeout) - self.register(timeout=timeout) - - def handle_timeout(self): - raise server.TimeoutException - - def handle_pollevents(self, events): - for e in events: - self.accept_connection() - - def shutdown(self): - self.sock.close() - self.sock.cleanup() - - def answer_stat_query(self, cs): - if self.repowatcher.timeout: - # We got a query while a rescan is pending. Make sure we - # rescan before responding, or we could give back a wrong - # answer. - self.repowatcher.handle_timeout() - return server.socketlistener.answer_stat_query(self, cs) - -class master(object): - def __init__(self, ui, dirstate, root, timeout=None): - self.ui = ui - self.repowatcher = repowatcher(ui, dirstate, root) - self.socketlistener = socketlistener(ui, root, self.repowatcher, - timeout) - - def shutdown(self): - for obj in pollable.instances.itervalues(): - try: - obj.shutdown() - except error.SignalInterrupt: - pass - - def run(self): - self.repowatcher.setup() - self.ui.note(_('finished setup\n')) - if os.getenv('TIME_STARTUP'): - sys.exit(0) - pollable.run() diff --git a/hgext/inotify/server.py b/hgext/inotify/server.py deleted file mode 100644 --- a/hgext/inotify/server.py +++ /dev/null @@ -1,465 +0,0 @@ -# server.py - common entry point for inotify status server -# -# Copyright 2009 Nicolas Dumazet -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -from mercurial.i18n import _ -from mercurial import cmdutil, posix, osutil, util -import common - -import errno -import os -import socket -import stat -import struct -import sys - -class AlreadyStartedException(Exception): - pass -class TimeoutException(Exception): - pass - -def join(a, b): - if a: - if a[-1] == '/': - return a + b - return a + '/' + b - return b - -def split(path): - c = path.rfind('/') - if c == -1: - return '', path - return path[:c], path[c + 1:] - -walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG) - -def walk(dirstate, absroot, root): - '''Like os.walk, but only yields regular files.''' - - # This function is critical to performance during startup. - - def walkit(root, reporoot): - files, dirs = [], [] - - try: - fullpath = join(absroot, root) - for name, kind in osutil.listdir(fullpath): - if kind == stat.S_IFDIR: - if name == '.hg': - if not reporoot: - return - else: - dirs.append(name) - path = join(root, name) - if dirstate._ignore(path): - continue - for result in walkit(path, False): - yield result - elif kind in (stat.S_IFREG, stat.S_IFLNK): - files.append(name) - yield fullpath, dirs, files - - except OSError, err: - 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: - raise - - return walkit(root, root == '') - -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 - - def walk(self, states, visited=None): - """ - 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(): - if visited is not None: - visited.add(dir.path) - for e in dir.walk(states): - yield e - - def lookup(self, states, path, visited): - """ - 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 - visited.add(tree.path) - return - - try: - # if path is a directory, walk it - target = tree.dirs[last] - visited.add(target.path) - for file, st in target.walk(states, visited): - yield file - except KeyError: - try: - if tree.files[last] in states: - # path is a file - visited.add(tree.path) - yield path - except KeyError: - # path is not tracked - pass - -class repowatcher(object): - """ - Watches inotify events - """ - statuskeys = 'almr!?' - - def __init__(self, ui, dirstate, root): - self.ui = ui - self.dirstate = dirstate - - self.wprefix = join(root, '') - self.prefixlen = len(self.wprefix) - - self.tree = directory() - self.statcache = {} - self.statustrees = dict([(s, directory()) for s in self.statuskeys]) - - self.ds_info = self.dirstate_info() - - self.last_event = None - - - def handle_timeout(self): - pass - - def dirstate_info(self): - try: - st = os.lstat(self.wprefix + '.hg/dirstate') - 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: - type_, mode, size, time = self.dirstate._map[fn][:4] - except KeyError: - type_ = '?' - if type_ == 'n': - st_mode, st_size, st_mtime = st - if size == -1: - return 'l' - 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.dirstate._dirignore(fn): - # we must check not only if the file is ignored, but if any part - # of its path match an ignore pattern - return 'i' - return type_ - - 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): - ''' - Update the stored status of a file. - - newstatus: - char in (statuskeys + 'ni'), new status to apply. - - or None, to stop tracking wfn - ''' - root, fn = split(wfn) - d = self.tree.dir(root) - - oldstatus = d.files.get(fn) - # oldstatus can be either: - # - None : fn is new - # - a char in statuskeys: fn is a (tracked) file - - if self.ui.debugflag and oldstatus != newstatus: - self.ui.note(_('status: %r %s -> %s\n') % - (wfn, oldstatus, newstatus)) - - if oldstatus and oldstatus in self.statuskeys \ - and oldstatus != newstatus: - del self.statustrees[oldstatus].dir(root).files[fn] - - if newstatus in (None, 'i'): - d.files.pop(fn, None) - elif oldstatus != newstatus: - d.files[fn] = newstatus - if newstatus != 'n': - self.statustrees[newstatus].dir(root).files[fn] = newstatus - - 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.statustrees[key].walk(key): - if wfn not in self.dirstate: - nuke.append(wfn) - for wfn in nuke: - root, fn = split(wfn) - del self.statustrees[key].dir(root).files[fn] - del self.tree.dir(root).files[fn] - - 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. - - if '_ignore' in self.dirstate.__dict__: - delattr(self.dirstate, '_ignore') - self.ui.note(_('rescanning due to .hgignore change\n')) - self.handle_timeout() - self.scan() - - 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 - - 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 - except OSError: - self.statcache.pop(wpath, None) - raise - -class socketlistener(object): - """ - Listens for client queries on unix socket inotify.sock - """ - def __init__(self, ui, root, repowatcher, timeout): - self.ui = ui - self.repowatcher = repowatcher - try: - self.sock = posix.unixdomainserver( - lambda p: os.path.join(root, '.hg', p), - 'inotify') - except (OSError, socket.error), err: - if err.args[0] == errno.EADDRINUSE: - raise AlreadyStartedException(_('cannot start: ' - 'socket is already bound')) - raise - self.fileno = self.sock.fileno - - def answer_stat_query(self, cs): - names = cs.read().split('\0') - - states = names.pop() - - self.ui.note(_('answering query for %r\n') % states) - - visited = set() - if not names: - def genresult(states, tree): - for fn, state in tree.walk(states): - yield fn - else: - def genresult(states, tree): - for fn in names: - for f in tree.lookup(states, fn, visited): - yield f - - return ['\0'.join(r) for r in [ - 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 [], - [], - 'c' in states and genresult('n', self.repowatcher.tree) or [], - visited - ]] - - def answer_dbug_query(self): - return ['\0'.join(self.repowatcher.debug())] - - def accept_connection(self): - 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) - 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 socket.error: - pass - return - - type = cs.read(4) - - if type == 'STAT': - results = self.answer_stat_query(cs) - elif type == 'DBUG': - results = self.answer_dbug_query() - else: - self.ui.warn(_('unrecognized query type: %s\n') % type) - return - - try: - try: - v = chr(common.version) - - sock.sendall(v + type + struct.pack(common.resphdrfmts[type], - *map(len, results))) - sock.sendall(''.join(results)) - finally: - sock.shutdown(socket.SHUT_WR) - except socket.error, err: - if err.args[0] != errno.EPIPE: - raise - -if sys.platform.startswith('linux'): - import linuxserver as _server -else: - raise ImportError - -master = _server.master - -def start(ui, dirstate, root, opts): - timeout = opts.get('idle_timeout') - if timeout: - timeout = float(timeout) * 60000 - else: - timeout = None - - class service(object): - def init(self): - try: - self.master = master(ui, dirstate, root, timeout) - except AlreadyStartedException, inst: - raise util.Abort("inotify-server: %s" % inst) - - def run(self): - try: - try: - self.master.run() - except TimeoutException: - pass - finally: - self.master.shutdown() - - if 'inserve' not in sys.argv: - runargs = util.hgcmd() + ['inserve', '-R', root] - else: - runargs = util.hgcmd() + sys.argv[1:] - - pidfile = ui.config('inotify', 'pidfile') - opts.setdefault('pid_file', '') - if opts['daemon'] and pidfile is not None and not opts['pid_file']: - opts['pid_file'] = pidfile - - service = service() - logfile = ui.config('inotify', 'log') - - appendpid = ui.configbool('inotify', 'appendpid', False) - - ui.debug('starting inotify server: %s\n' % ' '.join(runargs)) - cmdutil.service(opts, initfn=service.init, runfn=service.run, - logfile=logfile, runargs=runargs, appendpid=appendpid) diff --git a/mercurial/extensions.py b/mercurial/extensions.py --- a/mercurial/extensions.py +++ b/mercurial/extensions.py @@ -11,7 +11,7 @@ from i18n import _, gettext _extensions = {} _order = [] -_ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg'] +_ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify'] def extensions(ui=None): if ui: diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -467,20 +467,6 @@ class HackedMingw32CCompiler(cygwinccomp cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler -if sys.platform.startswith('linux') and os.uname()[2] > '2.6': - # The inotify extension is only usable with Linux 2.6 kernels. - # You also need a reasonably recent C library. - # In any case, if it fails to build the error will be skipped ('optional'). - cc = new_compiler() - if hasfunction(cc, 'inotify_add_watch'): - inotify = Extension('hgext.inotify.linux._inotify', - ['hgext/inotify/linux/_inotify.c'], - ['mercurial'], - depends=common_depends) - inotify.optional = True - extmodules.append(inotify) - packages.extend(['hgext.inotify', 'hgext.inotify.linux']) - packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo', 'help/*.txt']} diff --git a/tests/blacklists/inotify-failures b/tests/blacklists/inotify-failures deleted file mode 100644 --- a/tests/blacklists/inotify-failures +++ /dev/null @@ -1,21 +0,0 @@ -# When --inotify is activated, help output and config changes: -test-debugcomplete -test-empty -test-fncache -test-globalopts -test-help -test-hgrc -test-inherit-mode -test-qrecord -test-strict - -# --inotify activates de facto the inotify extension. It does not play well -# with inotify-specific tests, which activate/deactivate inotify at will: -test-inotify -test-inotify-debuginotify -test-inotify-dirty-dirstate -test-inotify-issue1208 -test-inotify-issue1371 -test-inotify-issue1542 -test-inotify-issue1556 -test-inotify-lookup diff --git a/tests/blacklists/linux-vfat b/tests/blacklists/linux-vfat --- a/tests/blacklists/linux-vfat +++ b/tests/blacklists/linux-vfat @@ -8,14 +8,6 @@ test-walk.t # no sockets or fifos test-hup.t -test-inotify-debuginotify.t -test-inotify-dirty-dirstate.t -test-inotify-issue1208.t -test-inotify-issue1371.t -test-inotify-issue1542.t -test-inotify-lookup.t -test-inotify.t -test-inotify-issue1556.t # no hardlinks test-hardlinks.t diff --git a/tests/hghave.py b/tests/hghave.py --- a/tests/hghave.py +++ b/tests/hghave.py @@ -96,21 +96,6 @@ def has_icasefs(): finally: os.remove(path) -def has_inotify(): - try: - import hgext.inotify.linux.watcher - except ImportError: - return False - name = tempfile.mktemp(dir='.', prefix=tempprefix) - sock = socket.socket(socket.AF_UNIX) - try: - sock.bind(name) - except socket.error: - return False - sock.close() - os.unlink(name) - return True - def has_fifo(): if getattr(os, "mkfifo", None) is None: return False @@ -315,7 +300,6 @@ checks = { "gpg": (has_gpg, "gpg client"), "hardlink": (has_hardlink, "hardlinks"), "icasefs": (has_icasefs, "case insensitive file system"), - "inotify": (has_inotify, "inotify extension support"), "killdaemons": (has_killdaemons, 'killdaemons.py support'), "lsprof": (has_lsprof, "python lsprof module"), "mtn": (has_mtn, "monotone client (>= 1.0)"), diff --git a/tests/run-tests.py b/tests/run-tests.py --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -152,8 +152,6 @@ def parseargs(): help="exit on the first test failure") parser.add_option("-H", "--htmlcov", action="store_true", help="create an HTML report of the coverage of the files") - parser.add_option("--inotify", action="store_true", - help="enable inotify extension when running tests") parser.add_option("-i", "--interactive", action="store_true", help="prompt to accept changed output") parser.add_option("-j", "--jobs", type="int", @@ -344,12 +342,6 @@ def createhgrc(path, options): hgrc.write('commit = -d "0 0"\n') hgrc.write('shelve = --date "0 0"\n') hgrc.write('tag = -d "0 0"\n') - if options.inotify: - hgrc.write('[extensions]\n') - hgrc.write('inotify=\n') - hgrc.write('[inotify]\n') - hgrc.write('pidfile=daemon.pids') - hgrc.write('appendpid=True\n') if options.extra_config_opt: for opt in options.extra_config_opt: section, key = opt.split('.', 1) diff --git a/tests/test-check-pyflakes.t b/tests/test-check-pyflakes.t --- a/tests/test-check-pyflakes.t +++ b/tests/test-check-pyflakes.t @@ -16,6 +16,5 @@ run pyflakes on all tracked files ending tests/hghave.py:*: 'pygments' imported but unused (glob) tests/hghave.py:*: 'ssl' imported but unused (glob) contrib/win32/hgwebdir_wsgi.py:*: 'from isapi.install import *' used; unable to detect undefined names (glob) - hgext/inotify/linux/__init__.py:*: 'from _inotify import *' used; unable to detect undefined names (glob) diff --git a/tests/test-duplicateoptions.py b/tests/test-duplicateoptions.py --- a/tests/test-duplicateoptions.py +++ b/tests/test-duplicateoptions.py @@ -1,7 +1,7 @@ import os from mercurial import ui, commands, extensions -ignore = set(['highlight', 'inotify', 'win32text', 'factotum']) +ignore = set(['highlight', 'win32text', 'factotum']) if os.name != 'nt': ignore.add('win32mbcs') diff --git a/tests/test-help.t b/tests/test-help.t --- a/tests/test-help.t +++ b/tests/test-help.t @@ -199,7 +199,7 @@ Short help: urls URL Paths Test extension help: - $ hg help extensions --config extensions.rebase= --config extensions.children= | grep -v inotify + $ hg help extensions --config extensions.rebase= --config extensions.children= Using Additional Features """"""""""""""""""""""""" diff --git a/tests/test-inotify-debuginotify.t b/tests/test-inotify-debuginotify.t deleted file mode 100644 --- a/tests/test-inotify-debuginotify.t +++ /dev/null @@ -1,41 +0,0 @@ - - $ "$TESTDIR/hghave" inotify || exit 80 - $ hg init - $ echo "[extensions]" >> $HGRCPATH - $ echo "inotify=" >> $HGRCPATH - -inserve - - $ hg inserve -d --pid-file=hg.pid - $ cat hg.pid >> "$DAEMON_PIDS" - -let the daemon finish its stuff - - $ sleep 1 - -empty - - $ hg debuginotify - directories being watched: - / - .hg/ - $ mkdir a - $ sleep 1 - -only 'a - - $ hg debuginotify - directories being watched: - / - .hg/ - a/ - $ rmdir a - $ sleep 1 - -empty again - - $ hg debuginotify - directories being watched: - / - .hg/ - $ "$TESTDIR/killdaemons.py" hg.pid diff --git a/tests/test-inotify-dirty-dirstate.t b/tests/test-inotify-dirty-dirstate.t deleted file mode 100644 --- a/tests/test-inotify-dirty-dirstate.t +++ /dev/null @@ -1,72 +0,0 @@ -issues when status queries are issued when dirstate is dirty - - $ "$TESTDIR/hghave" inotify || exit 80 - $ echo "[extensions]" >> $HGRCPATH - $ echo "inotify=" >> $HGRCPATH - $ echo "fetch=" >> $HGRCPATH - -issue1810: inotify and fetch - - $ hg init test; cd test - $ hg inserve -d --pid-file=../hg.pid - $ cat ../hg.pid >> "$DAEMON_PIDS" - $ echo foo > foo - $ hg add - adding foo - $ hg ci -m foo - $ cd .. - $ hg --config "inotify.pidfile=../hg2.pid" clone test test2 - updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cat ../hg2.pid >> "$DAEMON_PIDS" - $ cd test2 - $ echo bar > bar - $ hg add - adding bar - $ hg ci -m bar - $ cd ../test - $ echo spam > spam - $ hg add - adding spam - $ hg ci -m spam - $ cd ../test2 - $ hg st - -abort, outstanding changes - - $ hg fetch -q - $ hg st - $ cd .. - -issue1719: inotify and mq - - $ echo "mq=" >> $HGRCPATH - $ hg init test-1719 - $ cd test-1719 - -inserve - - $ hg inserve -d --pid-file=../hg-test-1719.pid - $ cat ../hg-test-1719.pid >> "$DAEMON_PIDS" - $ echo content > file - $ hg add file - $ hg qnew -f test.patch - $ hg status - $ hg qpop - popping test.patch - patch queue now empty - -st should not output anything - - $ hg status - $ hg qpush - applying test.patch - now at: test.patch - -st should not output anything - - $ hg status - $ hg qrefresh - $ hg status - - $ cd .. diff --git a/tests/test-inotify-issue1371.t b/tests/test-inotify-issue1371.t deleted file mode 100644 --- a/tests/test-inotify-issue1371.t +++ /dev/null @@ -1,44 +0,0 @@ - - $ "$TESTDIR/hghave" inotify || exit 80 - $ hg init - $ touch a b c d e f - $ echo "[extensions]" >> $HGRCPATH - $ echo "inotify=" >> $HGRCPATH - -inserve - - $ hg inserve -d --pid-file=hg.pid 2>&1 - $ cat hg.pid >> "$DAEMON_PIDS" - $ hg ci -Am m - adding a - adding b - adding c - adding d - adding e - adding f - adding hg.pid - -let the daemon finish its stuff - - $ sleep 1 - -eed to test all file operations - - $ hg rm a - $ rm b - $ echo c >> c - $ touch g - $ hg add g - $ hg mv e h - $ hg status - M c - A g - A h - R a - R e - ! b - $ sleep 1 - -Are we able to kill the service? if not, the service died on some error - - $ "$TESTDIR/killdaemons.py" hg.pid diff --git a/tests/test-inotify-issue1542.t b/tests/test-inotify-issue1542.t deleted file mode 100644 --- a/tests/test-inotify-issue1542.t +++ /dev/null @@ -1,36 +0,0 @@ - - $ "$TESTDIR/hghave" inotify || exit 80 - $ hg init - $ touch a - $ mkdir dir - $ touch dir/b - $ touch dir/c - $ echo "[extensions]" >> $HGRCPATH - $ echo "inotify=" >> $HGRCPATH - $ hg add dir/c - -inserve - - $ hg inserve -d --pid-file=hg.pid 2>&1 - $ cat hg.pid >> "$DAEMON_PIDS" - $ hg st - A dir/c - ? a - ? dir/b - ? hg.pid - -moving dir out - - $ mv dir ../tmp-test-inotify-issue1542 - -status - - $ hg st - ! dir/c - ? a - ? hg.pid - $ sleep 1 - -Are we able to kill the service? if not, the service died on some error - - $ "$TESTDIR/killdaemons.py" hg.pid diff --git a/tests/test-inotify-issue1556.t b/tests/test-inotify-issue1556.t deleted file mode 100644 --- a/tests/test-inotify-issue1556.t +++ /dev/null @@ -1,31 +0,0 @@ - - $ "$TESTDIR/hghave" inotify || exit 80 - $ hg init - $ touch a b - $ hg add a b - $ rm b - -status without inotify - - $ hg st - A a - ! b - $ echo "[extensions]" >> $HGRCPATH - $ echo "inotify=" >> $HGRCPATH - -inserve - - $ hg inserve -d --pid-file=hg.pid 2>&1 - $ cat hg.pid >> "$DAEMON_PIDS" - -status - - $ hg st - A a - ! b - ? hg.pid - $ sleep 1 - -Are we able to kill the service? if not, the service died on some error - - $ "$TESTDIR/killdaemons.py" hg.pid diff --git a/tests/test-inotify-lookup.t b/tests/test-inotify-lookup.t deleted file mode 100644 --- a/tests/test-inotify-lookup.t +++ /dev/null @@ -1,14 +0,0 @@ - - $ "$TESTDIR/hghave" inotify || exit 80 - $ hg init - $ echo "[extensions]" > .hg/hgrc - $ echo "inotify=" >> .hg/hgrc - $ hg inserve -d --pid-file .hg/inotify.pid - $ echo a > a - $ hg ci -Aqm0 - $ hg co -q null - $ hg co -q - $ hg st - $ cat a - a - $ "$TESTDIR/killdaemons.py" .hg/inotify.pid diff --git a/tests/test-inotify.t b/tests/test-inotify.t deleted file mode 100644 --- a/tests/test-inotify.t +++ /dev/null @@ -1,182 +0,0 @@ - - $ "$TESTDIR/hghave" inotify || exit 80 - $ hg init repo1 - $ cd repo1 - $ touch a b c d e - $ mkdir dir - $ mkdir dir/bar - $ touch dir/x dir/y dir/bar/foo - $ hg ci -Am m - adding a - adding b - adding c - adding d - adding dir/bar/foo - adding dir/x - adding dir/y - adding e - $ cd .. - $ hg clone repo1 repo2 - updating to branch default - 8 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ echo "[extensions]" >> $HGRCPATH - $ echo "inotify=" >> $HGRCPATH - $ cd repo2 - $ echo b >> a - -check that daemon started automatically works correctly -and make sure that inotify.pidfile works - - $ hg --config "inotify.pidfile=../hg2.pid" status - M a - -make sure that pidfile worked. Output should be silent. - - $ "$TESTDIR/killdaemons.py" ../hg2.pid - $ cd ../repo1 - -inserve - - $ hg inserve -d --pid-file=hg.pid - $ cat hg.pid >> "$DAEMON_PIDS" - -let the daemon finish its stuff - - $ sleep 1 - -cannot start, already bound - - $ hg inserve - abort: inotify-server: cannot start: socket is already bound - [255] - -issue907 - - $ hg status - ? hg.pid - -clean - - $ hg status -c - C a - C b - C c - C d - C dir/bar/foo - C dir/x - C dir/y - C e - -all - - $ hg status -A - ? hg.pid - C a - C b - C c - C d - C dir/bar/foo - C dir/x - C dir/y - C e - -path patterns - - $ echo x > dir/x - $ hg status . - M dir/x - ? hg.pid - $ hg status dir - M dir/x - $ cd dir - $ hg status . - M x - $ cd .. - -issue 1375 -testing that we can remove a folder and then add a file with the same name -issue 1375 - - $ mkdir h - $ echo h > h/h - $ hg ci -Am t - adding h/h - adding hg.pid - $ hg rm h - removing h/h - $ echo h >h - $ hg add h - $ hg status - A h - R h/h - $ hg ci -m0 - -Test for issue1735: inotify watches files in .hg/merge - - $ hg st - $ echo a > a - $ hg ci -Am a - $ hg st - $ echo b >> a - $ hg ci -m ab - $ hg st - $ echo c >> a - $ hg st - M a - $ HGMERGE=internal:local hg up 0 - 1 files updated, 1 files merged, 2 files removed, 0 files unresolved - $ hg st - M a - $ HGMERGE=internal:local hg up - 3 files updated, 1 files merged, 0 files removed, 0 files unresolved - $ hg st - M a - -Test for 1844: "hg ci folder" will not commit all changes beneath "folder" - - $ mkdir 1844 - $ echo a > 1844/foo - $ hg add 1844 - adding 1844/foo - $ hg ci -m 'working' - $ echo b >> 1844/foo - $ hg ci 1844 -m 'broken' - -Test for issue884: "Build products not ignored until .hgignore is touched" - - $ echo '^build$' > .hgignore - $ hg add .hgignore - $ hg ci .hgignore -m 'ignorelist' - -Now, lets add some build products... - - $ mkdir build - $ touch build/x - $ touch build/y - -build/x & build/y shouldn't appear in "hg st" - - $ hg st - $ "$TESTDIR/killdaemons.py" hg.pid - - $ cd .. - -Ensure that if the repo is in a directory whose name is too long, the -unix domain socket is reached through a symlink (issue1208). - - $ mkdir 0_3456789_10_456789_20_456789_30_456789_40_456789_50_45678_ - $ cd 0_3456789_10_456789_20_456789_30_456789_40_456789_50_45678_ - $ mkdir 60_456789_70_456789_80_456789_90_456789_100_56789_ - $ cd 60_456789_70_456789_80_456789_90_456789_100_56789_ - - $ hg --config inotify.pidfile=hg3.pid clone -q ../../repo1 - $ readlink repo1/.hg/inotify.sock - */inotify.sock (glob) - -Trying to start the server a second time should fail as usual. - - $ hg --cwd repo1 inserve - abort: inotify-server: cannot start: socket is already bound - [255] - - $ "$TESTDIR/killdaemons.py" hg3.pid