##// END OF EJS Templates
inotify: raise correct error if server is already started in a deep repository...
Nicolas Dumazet -
r12650:fed4bb2c default
parent child Browse files
Show More
@@ -1,489 +1,492 b''
1 # server.py - common entry point for inotify status server
1 # server.py - common entry point for inotify status server
2 #
2 #
3 # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com>
3 # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial import cmdutil, osutil, util
9 from mercurial import cmdutil, osutil, util
10 import common
10 import common
11
11
12 import errno
12 import errno
13 import os
13 import os
14 import socket
14 import socket
15 import stat
15 import stat
16 import struct
16 import struct
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19
19
20 class AlreadyStartedException(Exception):
20 class AlreadyStartedException(Exception):
21 pass
21 pass
22 class TimeoutException(Exception):
22 class TimeoutException(Exception):
23 pass
23 pass
24
24
25 def join(a, b):
25 def join(a, b):
26 if a:
26 if a:
27 if a[-1] == '/':
27 if a[-1] == '/':
28 return a + b
28 return a + b
29 return a + '/' + b
29 return a + '/' + b
30 return b
30 return b
31
31
32 def split(path):
32 def split(path):
33 c = path.rfind('/')
33 c = path.rfind('/')
34 if c == -1:
34 if c == -1:
35 return '', path
35 return '', path
36 return path[:c], path[c + 1:]
36 return path[:c], path[c + 1:]
37
37
38 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
38 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
39
39
40 def walk(dirstate, absroot, root):
40 def walk(dirstate, absroot, root):
41 '''Like os.walk, but only yields regular files.'''
41 '''Like os.walk, but only yields regular files.'''
42
42
43 # This function is critical to performance during startup.
43 # This function is critical to performance during startup.
44
44
45 def walkit(root, reporoot):
45 def walkit(root, reporoot):
46 files, dirs = [], []
46 files, dirs = [], []
47
47
48 try:
48 try:
49 fullpath = join(absroot, root)
49 fullpath = join(absroot, root)
50 for name, kind in osutil.listdir(fullpath):
50 for name, kind in osutil.listdir(fullpath):
51 if kind == stat.S_IFDIR:
51 if kind == stat.S_IFDIR:
52 if name == '.hg':
52 if name == '.hg':
53 if not reporoot:
53 if not reporoot:
54 return
54 return
55 else:
55 else:
56 dirs.append(name)
56 dirs.append(name)
57 path = join(root, name)
57 path = join(root, name)
58 if dirstate._ignore(path):
58 if dirstate._ignore(path):
59 continue
59 continue
60 for result in walkit(path, False):
60 for result in walkit(path, False):
61 yield result
61 yield result
62 elif kind in (stat.S_IFREG, stat.S_IFLNK):
62 elif kind in (stat.S_IFREG, stat.S_IFLNK):
63 files.append(name)
63 files.append(name)
64 yield fullpath, dirs, files
64 yield fullpath, dirs, files
65
65
66 except OSError, err:
66 except OSError, err:
67 if err.errno == errno.ENOTDIR:
67 if err.errno == errno.ENOTDIR:
68 # fullpath was a directory, but has since been replaced
68 # fullpath was a directory, but has since been replaced
69 # by a file.
69 # by a file.
70 yield fullpath, dirs, files
70 yield fullpath, dirs, files
71 elif err.errno not in walk_ignored_errors:
71 elif err.errno not in walk_ignored_errors:
72 raise
72 raise
73
73
74 return walkit(root, root == '')
74 return walkit(root, root == '')
75
75
76 class directory(object):
76 class directory(object):
77 """
77 """
78 Representing a directory
78 Representing a directory
79
79
80 * path is the relative path from repo root to this directory
80 * path is the relative path from repo root to this directory
81 * files is a dict listing the files in this directory
81 * files is a dict listing the files in this directory
82 - keys are file names
82 - keys are file names
83 - values are file status
83 - values are file status
84 * dirs is a dict listing the subdirectories
84 * dirs is a dict listing the subdirectories
85 - key are subdirectories names
85 - key are subdirectories names
86 - values are directory objects
86 - values are directory objects
87 """
87 """
88 def __init__(self, relpath=''):
88 def __init__(self, relpath=''):
89 self.path = relpath
89 self.path = relpath
90 self.files = {}
90 self.files = {}
91 self.dirs = {}
91 self.dirs = {}
92
92
93 def dir(self, relpath):
93 def dir(self, relpath):
94 """
94 """
95 Returns the directory contained at the relative path relpath.
95 Returns the directory contained at the relative path relpath.
96 Creates the intermediate directories if necessary.
96 Creates the intermediate directories if necessary.
97 """
97 """
98 if not relpath:
98 if not relpath:
99 return self
99 return self
100 l = relpath.split('/')
100 l = relpath.split('/')
101 ret = self
101 ret = self
102 while l:
102 while l:
103 next = l.pop(0)
103 next = l.pop(0)
104 try:
104 try:
105 ret = ret.dirs[next]
105 ret = ret.dirs[next]
106 except KeyError:
106 except KeyError:
107 d = directory(join(ret.path, next))
107 d = directory(join(ret.path, next))
108 ret.dirs[next] = d
108 ret.dirs[next] = d
109 ret = d
109 ret = d
110 return ret
110 return ret
111
111
112 def walk(self, states, visited=None):
112 def walk(self, states, visited=None):
113 """
113 """
114 yield (filename, status) pairs for items in the trees
114 yield (filename, status) pairs for items in the trees
115 that have status in states.
115 that have status in states.
116 filenames are relative to the repo root
116 filenames are relative to the repo root
117 """
117 """
118 for file, st in self.files.iteritems():
118 for file, st in self.files.iteritems():
119 if st in states:
119 if st in states:
120 yield join(self.path, file), st
120 yield join(self.path, file), st
121 for dir in self.dirs.itervalues():
121 for dir in self.dirs.itervalues():
122 if visited is not None:
122 if visited is not None:
123 visited.add(dir.path)
123 visited.add(dir.path)
124 for e in dir.walk(states):
124 for e in dir.walk(states):
125 yield e
125 yield e
126
126
127 def lookup(self, states, path, visited):
127 def lookup(self, states, path, visited):
128 """
128 """
129 yield root-relative filenames that match path, and whose
129 yield root-relative filenames that match path, and whose
130 status are in states:
130 status are in states:
131 * if path is a file, yield path
131 * if path is a file, yield path
132 * if path is a directory, yield directory files
132 * if path is a directory, yield directory files
133 * if path is not tracked, yield nothing
133 * if path is not tracked, yield nothing
134 """
134 """
135 if path[-1] == '/':
135 if path[-1] == '/':
136 path = path[:-1]
136 path = path[:-1]
137
137
138 paths = path.split('/')
138 paths = path.split('/')
139
139
140 # we need to check separately for last node
140 # we need to check separately for last node
141 last = paths.pop()
141 last = paths.pop()
142
142
143 tree = self
143 tree = self
144 try:
144 try:
145 for dir in paths:
145 for dir in paths:
146 tree = tree.dirs[dir]
146 tree = tree.dirs[dir]
147 except KeyError:
147 except KeyError:
148 # path is not tracked
148 # path is not tracked
149 visited.add(tree.path)
149 visited.add(tree.path)
150 return
150 return
151
151
152 try:
152 try:
153 # if path is a directory, walk it
153 # if path is a directory, walk it
154 target = tree.dirs[last]
154 target = tree.dirs[last]
155 visited.add(target.path)
155 visited.add(target.path)
156 for file, st in target.walk(states, visited):
156 for file, st in target.walk(states, visited):
157 yield file
157 yield file
158 except KeyError:
158 except KeyError:
159 try:
159 try:
160 if tree.files[last] in states:
160 if tree.files[last] in states:
161 # path is a file
161 # path is a file
162 visited.add(tree.path)
162 visited.add(tree.path)
163 yield path
163 yield path
164 except KeyError:
164 except KeyError:
165 # path is not tracked
165 # path is not tracked
166 pass
166 pass
167
167
168 class repowatcher(object):
168 class repowatcher(object):
169 """
169 """
170 Watches inotify events
170 Watches inotify events
171 """
171 """
172 statuskeys = 'almr!?'
172 statuskeys = 'almr!?'
173
173
174 def __init__(self, ui, dirstate, root):
174 def __init__(self, ui, dirstate, root):
175 self.ui = ui
175 self.ui = ui
176 self.dirstate = dirstate
176 self.dirstate = dirstate
177
177
178 self.wprefix = join(root, '')
178 self.wprefix = join(root, '')
179 self.prefixlen = len(self.wprefix)
179 self.prefixlen = len(self.wprefix)
180
180
181 self.tree = directory()
181 self.tree = directory()
182 self.statcache = {}
182 self.statcache = {}
183 self.statustrees = dict([(s, directory()) for s in self.statuskeys])
183 self.statustrees = dict([(s, directory()) for s in self.statuskeys])
184
184
185 self.ds_info = self.dirstate_info()
185 self.ds_info = self.dirstate_info()
186
186
187 self.last_event = None
187 self.last_event = None
188
188
189
189
190 def handle_timeout(self):
190 def handle_timeout(self):
191 pass
191 pass
192
192
193 def dirstate_info(self):
193 def dirstate_info(self):
194 try:
194 try:
195 st = os.lstat(self.wprefix + '.hg/dirstate')
195 st = os.lstat(self.wprefix + '.hg/dirstate')
196 return st.st_mtime, st.st_ino
196 return st.st_mtime, st.st_ino
197 except OSError, err:
197 except OSError, err:
198 if err.errno != errno.ENOENT:
198 if err.errno != errno.ENOENT:
199 raise
199 raise
200 return 0, 0
200 return 0, 0
201
201
202 def filestatus(self, fn, st):
202 def filestatus(self, fn, st):
203 try:
203 try:
204 type_, mode, size, time = self.dirstate._map[fn][:4]
204 type_, mode, size, time = self.dirstate._map[fn][:4]
205 except KeyError:
205 except KeyError:
206 type_ = '?'
206 type_ = '?'
207 if type_ == 'n':
207 if type_ == 'n':
208 st_mode, st_size, st_mtime = st
208 st_mode, st_size, st_mtime = st
209 if size == -1:
209 if size == -1:
210 return 'l'
210 return 'l'
211 if size and (size != st_size or (mode ^ st_mode) & 0100):
211 if size and (size != st_size or (mode ^ st_mode) & 0100):
212 return 'm'
212 return 'm'
213 if time != int(st_mtime):
213 if time != int(st_mtime):
214 return 'l'
214 return 'l'
215 return 'n'
215 return 'n'
216 if type_ == '?' and self.dirstate._dirignore(fn):
216 if type_ == '?' and self.dirstate._dirignore(fn):
217 # we must check not only if the file is ignored, but if any part
217 # we must check not only if the file is ignored, but if any part
218 # of its path match an ignore pattern
218 # of its path match an ignore pattern
219 return 'i'
219 return 'i'
220 return type_
220 return type_
221
221
222 def updatefile(self, wfn, osstat):
222 def updatefile(self, wfn, osstat):
223 '''
223 '''
224 update the file entry of an existing file.
224 update the file entry of an existing file.
225
225
226 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
226 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
227 '''
227 '''
228
228
229 self._updatestatus(wfn, self.filestatus(wfn, osstat))
229 self._updatestatus(wfn, self.filestatus(wfn, osstat))
230
230
231 def deletefile(self, wfn, oldstatus):
231 def deletefile(self, wfn, oldstatus):
232 '''
232 '''
233 update the entry of a file which has been deleted.
233 update the entry of a file which has been deleted.
234
234
235 oldstatus: char in statuskeys, status of the file before deletion
235 oldstatus: char in statuskeys, status of the file before deletion
236 '''
236 '''
237 if oldstatus == 'r':
237 if oldstatus == 'r':
238 newstatus = 'r'
238 newstatus = 'r'
239 elif oldstatus in 'almn':
239 elif oldstatus in 'almn':
240 newstatus = '!'
240 newstatus = '!'
241 else:
241 else:
242 newstatus = None
242 newstatus = None
243
243
244 self.statcache.pop(wfn, None)
244 self.statcache.pop(wfn, None)
245 self._updatestatus(wfn, newstatus)
245 self._updatestatus(wfn, newstatus)
246
246
247 def _updatestatus(self, wfn, newstatus):
247 def _updatestatus(self, wfn, newstatus):
248 '''
248 '''
249 Update the stored status of a file.
249 Update the stored status of a file.
250
250
251 newstatus: - char in (statuskeys + 'ni'), new status to apply.
251 newstatus: - char in (statuskeys + 'ni'), new status to apply.
252 - or None, to stop tracking wfn
252 - or None, to stop tracking wfn
253 '''
253 '''
254 root, fn = split(wfn)
254 root, fn = split(wfn)
255 d = self.tree.dir(root)
255 d = self.tree.dir(root)
256
256
257 oldstatus = d.files.get(fn)
257 oldstatus = d.files.get(fn)
258 # oldstatus can be either:
258 # oldstatus can be either:
259 # - None : fn is new
259 # - None : fn is new
260 # - a char in statuskeys: fn is a (tracked) file
260 # - a char in statuskeys: fn is a (tracked) file
261
261
262 if self.ui.debugflag and oldstatus != newstatus:
262 if self.ui.debugflag and oldstatus != newstatus:
263 self.ui.note(_('status: %r %s -> %s\n') %
263 self.ui.note(_('status: %r %s -> %s\n') %
264 (wfn, oldstatus, newstatus))
264 (wfn, oldstatus, newstatus))
265
265
266 if oldstatus and oldstatus in self.statuskeys \
266 if oldstatus and oldstatus in self.statuskeys \
267 and oldstatus != newstatus:
267 and oldstatus != newstatus:
268 del self.statustrees[oldstatus].dir(root).files[fn]
268 del self.statustrees[oldstatus].dir(root).files[fn]
269
269
270 if newstatus in (None, 'i'):
270 if newstatus in (None, 'i'):
271 d.files.pop(fn, None)
271 d.files.pop(fn, None)
272 elif oldstatus != newstatus:
272 elif oldstatus != newstatus:
273 d.files[fn] = newstatus
273 d.files[fn] = newstatus
274 if newstatus != 'n':
274 if newstatus != 'n':
275 self.statustrees[newstatus].dir(root).files[fn] = newstatus
275 self.statustrees[newstatus].dir(root).files[fn] = newstatus
276
276
277 def check_deleted(self, key):
277 def check_deleted(self, key):
278 # Files that had been deleted but were present in the dirstate
278 # Files that had been deleted but were present in the dirstate
279 # may have vanished from the dirstate; we must clean them up.
279 # may have vanished from the dirstate; we must clean them up.
280 nuke = []
280 nuke = []
281 for wfn, ignore in self.statustrees[key].walk(key):
281 for wfn, ignore in self.statustrees[key].walk(key):
282 if wfn not in self.dirstate:
282 if wfn not in self.dirstate:
283 nuke.append(wfn)
283 nuke.append(wfn)
284 for wfn in nuke:
284 for wfn in nuke:
285 root, fn = split(wfn)
285 root, fn = split(wfn)
286 del self.statustrees[key].dir(root).files[fn]
286 del self.statustrees[key].dir(root).files[fn]
287 del self.tree.dir(root).files[fn]
287 del self.tree.dir(root).files[fn]
288
288
289 def update_hgignore(self):
289 def update_hgignore(self):
290 # An update of the ignore file can potentially change the
290 # An update of the ignore file can potentially change the
291 # states of all unknown and ignored files.
291 # states of all unknown and ignored files.
292
292
293 # XXX If the user has other ignore files outside the repo, or
293 # XXX If the user has other ignore files outside the repo, or
294 # changes their list of ignore files at run time, we'll
294 # changes their list of ignore files at run time, we'll
295 # potentially never see changes to them. We could get the
295 # potentially never see changes to them. We could get the
296 # client to report to us what ignore data they're using.
296 # client to report to us what ignore data they're using.
297 # But it's easier to do nothing than to open that can of
297 # But it's easier to do nothing than to open that can of
298 # worms.
298 # worms.
299
299
300 if '_ignore' in self.dirstate.__dict__:
300 if '_ignore' in self.dirstate.__dict__:
301 delattr(self.dirstate, '_ignore')
301 delattr(self.dirstate, '_ignore')
302 self.ui.note(_('rescanning due to .hgignore change\n'))
302 self.ui.note(_('rescanning due to .hgignore change\n'))
303 self.handle_timeout()
303 self.handle_timeout()
304 self.scan()
304 self.scan()
305
305
306 def getstat(self, wpath):
306 def getstat(self, wpath):
307 try:
307 try:
308 return self.statcache[wpath]
308 return self.statcache[wpath]
309 except KeyError:
309 except KeyError:
310 try:
310 try:
311 return self.stat(wpath)
311 return self.stat(wpath)
312 except OSError, err:
312 except OSError, err:
313 if err.errno != errno.ENOENT:
313 if err.errno != errno.ENOENT:
314 raise
314 raise
315
315
316 def stat(self, wpath):
316 def stat(self, wpath):
317 try:
317 try:
318 st = os.lstat(join(self.wprefix, wpath))
318 st = os.lstat(join(self.wprefix, wpath))
319 ret = st.st_mode, st.st_size, st.st_mtime
319 ret = st.st_mode, st.st_size, st.st_mtime
320 self.statcache[wpath] = ret
320 self.statcache[wpath] = ret
321 return ret
321 return ret
322 except OSError:
322 except OSError:
323 self.statcache.pop(wpath, None)
323 self.statcache.pop(wpath, None)
324 raise
324 raise
325
325
326 class socketlistener(object):
326 class socketlistener(object):
327 """
327 """
328 Listens for client queries on unix socket inotify.sock
328 Listens for client queries on unix socket inotify.sock
329 """
329 """
330 def __init__(self, ui, root, repowatcher, timeout):
330 def __init__(self, ui, root, repowatcher, timeout):
331 self.ui = ui
331 self.ui = ui
332 self.repowatcher = repowatcher
332 self.repowatcher = repowatcher
333 self.sock = socket.socket(socket.AF_UNIX)
333 self.sock = socket.socket(socket.AF_UNIX)
334 self.sockpath = join(root, '.hg/inotify.sock')
334 self.sockpath = join(root, '.hg/inotify.sock')
335 self.realsockpath = None
335
336 self.realsockpath = self.sockpath
337 if os.path.islink(self.sockpath):
338 if os.path.exists(self.sockpath):
339 self.realsockpath = os.readlink(self.sockpath)
340 else:
341 raise util.Abort('inotify-server: cannot start: '
342 '.hg/inotify.sock is a broken symlink')
336 try:
343 try:
337 self.sock.bind(self.sockpath)
344 self.sock.bind(self.realsockpath)
338 except socket.error, err:
345 except socket.error, err:
339 if err.args[0] == errno.EADDRINUSE:
346 if err.args[0] == errno.EADDRINUSE:
340 raise AlreadyStartedException(_('cannot start: socket is '
347 raise AlreadyStartedException(_('cannot start: socket is '
341 'already bound'))
348 'already bound'))
342 if err.args[0] == "AF_UNIX path too long":
349 if err.args[0] == "AF_UNIX path too long":
343 if os.path.islink(self.sockpath) and \
344 not os.path.exists(self.sockpath):
345 raise util.Abort('inotify-server: cannot start: '
346 '.hg/inotify.sock is a broken symlink')
347 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
350 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
348 self.realsockpath = os.path.join(tempdir, "inotify.sock")
351 self.realsockpath = os.path.join(tempdir, "inotify.sock")
349 try:
352 try:
350 self.sock.bind(self.realsockpath)
353 self.sock.bind(self.realsockpath)
351 os.symlink(self.realsockpath, self.sockpath)
354 os.symlink(self.realsockpath, self.sockpath)
352 except (OSError, socket.error), inst:
355 except (OSError, socket.error), inst:
353 try:
356 try:
354 os.unlink(self.realsockpath)
357 os.unlink(self.realsockpath)
355 except:
358 except:
356 pass
359 pass
357 os.rmdir(tempdir)
360 os.rmdir(tempdir)
358 if inst.errno == errno.EEXIST:
361 if inst.errno == errno.EEXIST:
359 raise AlreadyStartedException(_('cannot start: tried '
362 raise AlreadyStartedException(_('cannot start: tried '
360 'linking .hg/inotify.sock to a temporary socket but'
363 'linking .hg/inotify.sock to a temporary socket but'
361 ' .hg/inotify.sock already exists'))
364 ' .hg/inotify.sock already exists'))
362 raise
365 raise
363 else:
366 else:
364 raise
367 raise
365 self.sock.listen(5)
368 self.sock.listen(5)
366 self.fileno = self.sock.fileno
369 self.fileno = self.sock.fileno
367
370
368 def answer_stat_query(self, cs):
371 def answer_stat_query(self, cs):
369 names = cs.read().split('\0')
372 names = cs.read().split('\0')
370
373
371 states = names.pop()
374 states = names.pop()
372
375
373 self.ui.note(_('answering query for %r\n') % states)
376 self.ui.note(_('answering query for %r\n') % states)
374
377
375 visited = set()
378 visited = set()
376 if not names:
379 if not names:
377 def genresult(states, tree):
380 def genresult(states, tree):
378 for fn, state in tree.walk(states):
381 for fn, state in tree.walk(states):
379 yield fn
382 yield fn
380 else:
383 else:
381 def genresult(states, tree):
384 def genresult(states, tree):
382 for fn in names:
385 for fn in names:
383 for f in tree.lookup(states, fn, visited):
386 for f in tree.lookup(states, fn, visited):
384 yield f
387 yield f
385
388
386 return ['\0'.join(r) for r in [
389 return ['\0'.join(r) for r in [
387 genresult('l', self.repowatcher.statustrees['l']),
390 genresult('l', self.repowatcher.statustrees['l']),
388 genresult('m', self.repowatcher.statustrees['m']),
391 genresult('m', self.repowatcher.statustrees['m']),
389 genresult('a', self.repowatcher.statustrees['a']),
392 genresult('a', self.repowatcher.statustrees['a']),
390 genresult('r', self.repowatcher.statustrees['r']),
393 genresult('r', self.repowatcher.statustrees['r']),
391 genresult('!', self.repowatcher.statustrees['!']),
394 genresult('!', self.repowatcher.statustrees['!']),
392 '?' in states
395 '?' in states
393 and genresult('?', self.repowatcher.statustrees['?'])
396 and genresult('?', self.repowatcher.statustrees['?'])
394 or [],
397 or [],
395 [],
398 [],
396 'c' in states and genresult('n', self.repowatcher.tree) or [],
399 'c' in states and genresult('n', self.repowatcher.tree) or [],
397 visited
400 visited
398 ]]
401 ]]
399
402
400 def answer_dbug_query(self):
403 def answer_dbug_query(self):
401 return ['\0'.join(self.repowatcher.debug())]
404 return ['\0'.join(self.repowatcher.debug())]
402
405
403 def accept_connection(self):
406 def accept_connection(self):
404 sock, addr = self.sock.accept()
407 sock, addr = self.sock.accept()
405
408
406 cs = common.recvcs(sock)
409 cs = common.recvcs(sock)
407 version = ord(cs.read(1))
410 version = ord(cs.read(1))
408
411
409 if version != common.version:
412 if version != common.version:
410 self.ui.warn(_('received query from incompatible client '
413 self.ui.warn(_('received query from incompatible client '
411 'version %d\n') % version)
414 'version %d\n') % version)
412 try:
415 try:
413 # try to send back our version to the client
416 # try to send back our version to the client
414 # this way, the client too is informed of the mismatch
417 # this way, the client too is informed of the mismatch
415 sock.sendall(chr(common.version))
418 sock.sendall(chr(common.version))
416 except:
419 except:
417 pass
420 pass
418 return
421 return
419
422
420 type = cs.read(4)
423 type = cs.read(4)
421
424
422 if type == 'STAT':
425 if type == 'STAT':
423 results = self.answer_stat_query(cs)
426 results = self.answer_stat_query(cs)
424 elif type == 'DBUG':
427 elif type == 'DBUG':
425 results = self.answer_dbug_query()
428 results = self.answer_dbug_query()
426 else:
429 else:
427 self.ui.warn(_('unrecognized query type: %s\n') % type)
430 self.ui.warn(_('unrecognized query type: %s\n') % type)
428 return
431 return
429
432
430 try:
433 try:
431 try:
434 try:
432 v = chr(common.version)
435 v = chr(common.version)
433
436
434 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
437 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
435 *map(len, results)))
438 *map(len, results)))
436 sock.sendall(''.join(results))
439 sock.sendall(''.join(results))
437 finally:
440 finally:
438 sock.shutdown(socket.SHUT_WR)
441 sock.shutdown(socket.SHUT_WR)
439 except socket.error, err:
442 except socket.error, err:
440 if err.args[0] != errno.EPIPE:
443 if err.args[0] != errno.EPIPE:
441 raise
444 raise
442
445
443 if sys.platform == 'linux2':
446 if sys.platform == 'linux2':
444 import linuxserver as _server
447 import linuxserver as _server
445 else:
448 else:
446 raise ImportError
449 raise ImportError
447
450
448 master = _server.master
451 master = _server.master
449
452
450 def start(ui, dirstate, root, opts):
453 def start(ui, dirstate, root, opts):
451 timeout = opts.get('idle_timeout')
454 timeout = opts.get('idle_timeout')
452 if timeout:
455 if timeout:
453 timeout = float(timeout) * 60000
456 timeout = float(timeout) * 60000
454 else:
457 else:
455 timeout = None
458 timeout = None
456
459
457 class service(object):
460 class service(object):
458 def init(self):
461 def init(self):
459 try:
462 try:
460 self.master = master(ui, dirstate, root, timeout)
463 self.master = master(ui, dirstate, root, timeout)
461 except AlreadyStartedException, inst:
464 except AlreadyStartedException, inst:
462 raise util.Abort("inotify-server: %s" % inst)
465 raise util.Abort("inotify-server: %s" % inst)
463
466
464 def run(self):
467 def run(self):
465 try:
468 try:
466 try:
469 try:
467 self.master.run()
470 self.master.run()
468 except TimeoutException:
471 except TimeoutException:
469 pass
472 pass
470 finally:
473 finally:
471 self.master.shutdown()
474 self.master.shutdown()
472
475
473 if 'inserve' not in sys.argv:
476 if 'inserve' not in sys.argv:
474 runargs = util.hgcmd() + ['inserve', '-R', root]
477 runargs = util.hgcmd() + ['inserve', '-R', root]
475 else:
478 else:
476 runargs = util.hgcmd() + sys.argv[1:]
479 runargs = util.hgcmd() + sys.argv[1:]
477
480
478 pidfile = ui.config('inotify', 'pidfile')
481 pidfile = ui.config('inotify', 'pidfile')
479 if opts['daemon'] and pidfile is not None and 'pid-file' not in runargs:
482 if opts['daemon'] and pidfile is not None and 'pid-file' not in runargs:
480 runargs.append("--pid-file=%s" % pidfile)
483 runargs.append("--pid-file=%s" % pidfile)
481
484
482 service = service()
485 service = service()
483 logfile = ui.config('inotify', 'log')
486 logfile = ui.config('inotify', 'log')
484
487
485 appendpid = ui.configbool('inotify', 'appendpid', False)
488 appendpid = ui.configbool('inotify', 'appendpid', False)
486
489
487 ui.debug('starting inotify server: %s\n' % ' '.join(runargs))
490 ui.debug('starting inotify server: %s\n' % ' '.join(runargs))
488 cmdutil.service(opts, initfn=service.init, runfn=service.run,
491 cmdutil.service(opts, initfn=service.init, runfn=service.run,
489 logfile=logfile, runargs=runargs, appendpid=appendpid)
492 logfile=logfile, runargs=runargs, appendpid=appendpid)
@@ -1,29 +1,36 b''
1
1
2 $ "$TESTDIR/hghave" inotify || exit 80
2 $ "$TESTDIR/hghave" inotify || exit 80
3 $ echo "[extensions]" >> $HGRCPATH
3 $ echo "[extensions]" >> $HGRCPATH
4 $ echo "inotify=" >> $HGRCPATH
4 $ echo "inotify=" >> $HGRCPATH
5 $ p="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
5 $ p="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
6 $ hg init $p
6 $ hg init $p
7 $ cd $p
7 $ cd $p
8
8
9 fail
9 fail
10
10
11 $ ln -sf doesnotexist .hg/inotify.sock
11 $ ln -sf doesnotexist .hg/inotify.sock
12 $ hg st
12 $ hg st
13 abort: inotify-server: cannot start: .hg/inotify.sock is a broken symlink
13 abort: inotify-server: cannot start: .hg/inotify.sock is a broken symlink
14 inotify-client: could not start inotify server: child process failed to start
14 inotify-client: could not start inotify server: child process failed to start
15 $ hg inserve
15 $ hg inserve
16 abort: inotify-server: cannot start: .hg/inotify.sock is a broken symlink
16 abort: inotify-server: cannot start: .hg/inotify.sock is a broken symlink
17 [255]
17 [255]
18 $ rm .hg/inotify.sock
18 $ rm .hg/inotify.sock
19
19
20 inserve
20 inserve
21
21
22 $ hg inserve -d --pid-file=hg.pid
22 $ hg inserve -d --pid-file=hg.pid
23 $ cat hg.pid >> "$DAEMON_PIDS"
23 $ cat hg.pid >> "$DAEMON_PIDS"
24
24
25 status
25 status
26
26
27 $ hg status
27 $ hg status
28 ? hg.pid
28 ? hg.pid
29
30 if we try to start twice the server, make sure we get a correct error
31
32 $ hg inserve -d --pid-file=hg2.pid
33 abort: inotify-server: cannot start: socket is already bound
34 abort: child process failed to start
35 [255]
29 $ kill `cat hg.pid`
36 $ kill `cat hg.pid`
General Comments 0
You need to be logged in to leave comments. Login now