##// END OF EJS Templates
inotify: check all components of filenames against hgignore (issue884)...
Renato Cunha -
r11545:db9d1623 stable
parent child Browse files
Show More
@@ -1,486 +1,488 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._ignore(fn):
216 if type_ == '?' and self.dirstate._dirignore(fn):
217 # we must check not only if the file is ignored, but if any part
218 # of its path match an ignore pattern
217 return 'i'
219 return 'i'
218 return type_
220 return type_
219
221
220 def updatefile(self, wfn, osstat):
222 def updatefile(self, wfn, osstat):
221 '''
223 '''
222 update the file entry of an existing file.
224 update the file entry of an existing file.
223
225
224 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
226 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
225 '''
227 '''
226
228
227 self._updatestatus(wfn, self.filestatus(wfn, osstat))
229 self._updatestatus(wfn, self.filestatus(wfn, osstat))
228
230
229 def deletefile(self, wfn, oldstatus):
231 def deletefile(self, wfn, oldstatus):
230 '''
232 '''
231 update the entry of a file which has been deleted.
233 update the entry of a file which has been deleted.
232
234
233 oldstatus: char in statuskeys, status of the file before deletion
235 oldstatus: char in statuskeys, status of the file before deletion
234 '''
236 '''
235 if oldstatus == 'r':
237 if oldstatus == 'r':
236 newstatus = 'r'
238 newstatus = 'r'
237 elif oldstatus in 'almn':
239 elif oldstatus in 'almn':
238 newstatus = '!'
240 newstatus = '!'
239 else:
241 else:
240 newstatus = None
242 newstatus = None
241
243
242 self.statcache.pop(wfn, None)
244 self.statcache.pop(wfn, None)
243 self._updatestatus(wfn, newstatus)
245 self._updatestatus(wfn, newstatus)
244
246
245 def _updatestatus(self, wfn, newstatus):
247 def _updatestatus(self, wfn, newstatus):
246 '''
248 '''
247 Update the stored status of a file.
249 Update the stored status of a file.
248
250
249 newstatus: - char in (statuskeys + 'ni'), new status to apply.
251 newstatus: - char in (statuskeys + 'ni'), new status to apply.
250 - or None, to stop tracking wfn
252 - or None, to stop tracking wfn
251 '''
253 '''
252 root, fn = split(wfn)
254 root, fn = split(wfn)
253 d = self.tree.dir(root)
255 d = self.tree.dir(root)
254
256
255 oldstatus = d.files.get(fn)
257 oldstatus = d.files.get(fn)
256 # oldstatus can be either:
258 # oldstatus can be either:
257 # - None : fn is new
259 # - None : fn is new
258 # - a char in statuskeys: fn is a (tracked) file
260 # - a char in statuskeys: fn is a (tracked) file
259
261
260 if self.ui.debugflag and oldstatus != newstatus:
262 if self.ui.debugflag and oldstatus != newstatus:
261 self.ui.note(_('status: %r %s -> %s\n') %
263 self.ui.note(_('status: %r %s -> %s\n') %
262 (wfn, oldstatus, newstatus))
264 (wfn, oldstatus, newstatus))
263
265
264 if oldstatus and oldstatus in self.statuskeys \
266 if oldstatus and oldstatus in self.statuskeys \
265 and oldstatus != newstatus:
267 and oldstatus != newstatus:
266 del self.statustrees[oldstatus].dir(root).files[fn]
268 del self.statustrees[oldstatus].dir(root).files[fn]
267
269
268 if newstatus in (None, 'i'):
270 if newstatus in (None, 'i'):
269 d.files.pop(fn, None)
271 d.files.pop(fn, None)
270 elif oldstatus != newstatus:
272 elif oldstatus != newstatus:
271 d.files[fn] = newstatus
273 d.files[fn] = newstatus
272 if newstatus != 'n':
274 if newstatus != 'n':
273 self.statustrees[newstatus].dir(root).files[fn] = newstatus
275 self.statustrees[newstatus].dir(root).files[fn] = newstatus
274
276
275 def check_deleted(self, key):
277 def check_deleted(self, key):
276 # Files that had been deleted but were present in the dirstate
278 # Files that had been deleted but were present in the dirstate
277 # may have vanished from the dirstate; we must clean them up.
279 # may have vanished from the dirstate; we must clean them up.
278 nuke = []
280 nuke = []
279 for wfn, ignore in self.statustrees[key].walk(key):
281 for wfn, ignore in self.statustrees[key].walk(key):
280 if wfn not in self.dirstate:
282 if wfn not in self.dirstate:
281 nuke.append(wfn)
283 nuke.append(wfn)
282 for wfn in nuke:
284 for wfn in nuke:
283 root, fn = split(wfn)
285 root, fn = split(wfn)
284 del self.statustrees[key].dir(root).files[fn]
286 del self.statustrees[key].dir(root).files[fn]
285 del self.tree.dir(root).files[fn]
287 del self.tree.dir(root).files[fn]
286
288
287 def update_hgignore(self):
289 def update_hgignore(self):
288 # An update of the ignore file can potentially change the
290 # An update of the ignore file can potentially change the
289 # states of all unknown and ignored files.
291 # states of all unknown and ignored files.
290
292
291 # 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
292 # changes their list of ignore files at run time, we'll
294 # changes their list of ignore files at run time, we'll
293 # potentially never see changes to them. We could get the
295 # potentially never see changes to them. We could get the
294 # client to report to us what ignore data they're using.
296 # client to report to us what ignore data they're using.
295 # 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
296 # worms.
298 # worms.
297
299
298 if '_ignore' in self.dirstate.__dict__:
300 if '_ignore' in self.dirstate.__dict__:
299 delattr(self.dirstate, '_ignore')
301 delattr(self.dirstate, '_ignore')
300 self.ui.note(_('rescanning due to .hgignore change\n'))
302 self.ui.note(_('rescanning due to .hgignore change\n'))
301 self.handle_timeout()
303 self.handle_timeout()
302 self.scan()
304 self.scan()
303
305
304 def getstat(self, wpath):
306 def getstat(self, wpath):
305 try:
307 try:
306 return self.statcache[wpath]
308 return self.statcache[wpath]
307 except KeyError:
309 except KeyError:
308 try:
310 try:
309 return self.stat(wpath)
311 return self.stat(wpath)
310 except OSError, err:
312 except OSError, err:
311 if err.errno != errno.ENOENT:
313 if err.errno != errno.ENOENT:
312 raise
314 raise
313
315
314 def stat(self, wpath):
316 def stat(self, wpath):
315 try:
317 try:
316 st = os.lstat(join(self.wprefix, wpath))
318 st = os.lstat(join(self.wprefix, wpath))
317 ret = st.st_mode, st.st_size, st.st_mtime
319 ret = st.st_mode, st.st_size, st.st_mtime
318 self.statcache[wpath] = ret
320 self.statcache[wpath] = ret
319 return ret
321 return ret
320 except OSError:
322 except OSError:
321 self.statcache.pop(wpath, None)
323 self.statcache.pop(wpath, None)
322 raise
324 raise
323
325
324 class socketlistener(object):
326 class socketlistener(object):
325 """
327 """
326 Listens for client queries on unix socket inotify.sock
328 Listens for client queries on unix socket inotify.sock
327 """
329 """
328 def __init__(self, ui, root, repowatcher, timeout):
330 def __init__(self, ui, root, repowatcher, timeout):
329 self.ui = ui
331 self.ui = ui
330 self.repowatcher = repowatcher
332 self.repowatcher = repowatcher
331 self.sock = socket.socket(socket.AF_UNIX)
333 self.sock = socket.socket(socket.AF_UNIX)
332 self.sockpath = join(root, '.hg/inotify.sock')
334 self.sockpath = join(root, '.hg/inotify.sock')
333 self.realsockpath = None
335 self.realsockpath = None
334 try:
336 try:
335 self.sock.bind(self.sockpath)
337 self.sock.bind(self.sockpath)
336 except socket.error, err:
338 except socket.error, err:
337 if err[0] == errno.EADDRINUSE:
339 if err[0] == errno.EADDRINUSE:
338 raise AlreadyStartedException(_('cannot start: socket is '
340 raise AlreadyStartedException(_('cannot start: socket is '
339 'already bound'))
341 'already bound'))
340 if err[0] == "AF_UNIX path too long":
342 if err[0] == "AF_UNIX path too long":
341 if os.path.islink(self.sockpath) and \
343 if os.path.islink(self.sockpath) and \
342 not os.path.exists(self.sockpath):
344 not os.path.exists(self.sockpath):
343 raise util.Abort('inotify-server: cannot start: '
345 raise util.Abort('inotify-server: cannot start: '
344 '.hg/inotify.sock is a broken symlink')
346 '.hg/inotify.sock is a broken symlink')
345 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
347 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
346 self.realsockpath = os.path.join(tempdir, "inotify.sock")
348 self.realsockpath = os.path.join(tempdir, "inotify.sock")
347 try:
349 try:
348 self.sock.bind(self.realsockpath)
350 self.sock.bind(self.realsockpath)
349 os.symlink(self.realsockpath, self.sockpath)
351 os.symlink(self.realsockpath, self.sockpath)
350 except (OSError, socket.error), inst:
352 except (OSError, socket.error), inst:
351 try:
353 try:
352 os.unlink(self.realsockpath)
354 os.unlink(self.realsockpath)
353 except:
355 except:
354 pass
356 pass
355 os.rmdir(tempdir)
357 os.rmdir(tempdir)
356 if inst.errno == errno.EEXIST:
358 if inst.errno == errno.EEXIST:
357 raise AlreadyStartedException(_('cannot start: tried '
359 raise AlreadyStartedException(_('cannot start: tried '
358 'linking .hg/inotify.sock to a temporary socket but'
360 'linking .hg/inotify.sock to a temporary socket but'
359 ' .hg/inotify.sock already exists'))
361 ' .hg/inotify.sock already exists'))
360 raise
362 raise
361 else:
363 else:
362 raise
364 raise
363 self.sock.listen(5)
365 self.sock.listen(5)
364 self.fileno = self.sock.fileno
366 self.fileno = self.sock.fileno
365
367
366 def answer_stat_query(self, cs):
368 def answer_stat_query(self, cs):
367 names = cs.read().split('\0')
369 names = cs.read().split('\0')
368
370
369 states = names.pop()
371 states = names.pop()
370
372
371 self.ui.note(_('answering query for %r\n') % states)
373 self.ui.note(_('answering query for %r\n') % states)
372
374
373 visited = set()
375 visited = set()
374 if not names:
376 if not names:
375 def genresult(states, tree):
377 def genresult(states, tree):
376 for fn, state in tree.walk(states):
378 for fn, state in tree.walk(states):
377 yield fn
379 yield fn
378 else:
380 else:
379 def genresult(states, tree):
381 def genresult(states, tree):
380 for fn in names:
382 for fn in names:
381 for f in tree.lookup(states, fn, visited):
383 for f in tree.lookup(states, fn, visited):
382 yield f
384 yield f
383
385
384 return ['\0'.join(r) for r in [
386 return ['\0'.join(r) for r in [
385 genresult('l', self.repowatcher.statustrees['l']),
387 genresult('l', self.repowatcher.statustrees['l']),
386 genresult('m', self.repowatcher.statustrees['m']),
388 genresult('m', self.repowatcher.statustrees['m']),
387 genresult('a', self.repowatcher.statustrees['a']),
389 genresult('a', self.repowatcher.statustrees['a']),
388 genresult('r', self.repowatcher.statustrees['r']),
390 genresult('r', self.repowatcher.statustrees['r']),
389 genresult('!', self.repowatcher.statustrees['!']),
391 genresult('!', self.repowatcher.statustrees['!']),
390 '?' in states
392 '?' in states
391 and genresult('?', self.repowatcher.statustrees['?'])
393 and genresult('?', self.repowatcher.statustrees['?'])
392 or [],
394 or [],
393 [],
395 [],
394 'c' in states and genresult('n', self.repowatcher.tree) or [],
396 'c' in states and genresult('n', self.repowatcher.tree) or [],
395 visited
397 visited
396 ]]
398 ]]
397
399
398 def answer_dbug_query(self):
400 def answer_dbug_query(self):
399 return ['\0'.join(self.repowatcher.debug())]
401 return ['\0'.join(self.repowatcher.debug())]
400
402
401 def accept_connection(self):
403 def accept_connection(self):
402 sock, addr = self.sock.accept()
404 sock, addr = self.sock.accept()
403
405
404 cs = common.recvcs(sock)
406 cs = common.recvcs(sock)
405 version = ord(cs.read(1))
407 version = ord(cs.read(1))
406
408
407 if version != common.version:
409 if version != common.version:
408 self.ui.warn(_('received query from incompatible client '
410 self.ui.warn(_('received query from incompatible client '
409 'version %d\n') % version)
411 'version %d\n') % version)
410 try:
412 try:
411 # try to send back our version to the client
413 # try to send back our version to the client
412 # this way, the client too is informed of the mismatch
414 # this way, the client too is informed of the mismatch
413 sock.sendall(chr(common.version))
415 sock.sendall(chr(common.version))
414 except:
416 except:
415 pass
417 pass
416 return
418 return
417
419
418 type = cs.read(4)
420 type = cs.read(4)
419
421
420 if type == 'STAT':
422 if type == 'STAT':
421 results = self.answer_stat_query(cs)
423 results = self.answer_stat_query(cs)
422 elif type == 'DBUG':
424 elif type == 'DBUG':
423 results = self.answer_dbug_query()
425 results = self.answer_dbug_query()
424 else:
426 else:
425 self.ui.warn(_('unrecognized query type: %s\n') % type)
427 self.ui.warn(_('unrecognized query type: %s\n') % type)
426 return
428 return
427
429
428 try:
430 try:
429 try:
431 try:
430 v = chr(common.version)
432 v = chr(common.version)
431
433
432 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
434 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
433 *map(len, results)))
435 *map(len, results)))
434 sock.sendall(''.join(results))
436 sock.sendall(''.join(results))
435 finally:
437 finally:
436 sock.shutdown(socket.SHUT_WR)
438 sock.shutdown(socket.SHUT_WR)
437 except socket.error, err:
439 except socket.error, err:
438 if err[0] != errno.EPIPE:
440 if err[0] != errno.EPIPE:
439 raise
441 raise
440
442
441 if sys.platform == 'linux2':
443 if sys.platform == 'linux2':
442 import linuxserver as _server
444 import linuxserver as _server
443 else:
445 else:
444 raise ImportError
446 raise ImportError
445
447
446 master = _server.master
448 master = _server.master
447
449
448 def start(ui, dirstate, root, opts):
450 def start(ui, dirstate, root, opts):
449 timeout = opts.get('idle_timeout')
451 timeout = opts.get('idle_timeout')
450 if timeout:
452 if timeout:
451 timeout = float(timeout) * 60000
453 timeout = float(timeout) * 60000
452 else:
454 else:
453 timeout = None
455 timeout = None
454
456
455 class service(object):
457 class service(object):
456 def init(self):
458 def init(self):
457 try:
459 try:
458 self.master = master(ui, dirstate, root, timeout)
460 self.master = master(ui, dirstate, root, timeout)
459 except AlreadyStartedException, inst:
461 except AlreadyStartedException, inst:
460 raise util.Abort("inotify-server: %s" % inst)
462 raise util.Abort("inotify-server: %s" % inst)
461
463
462 def run(self):
464 def run(self):
463 try:
465 try:
464 try:
466 try:
465 self.master.run()
467 self.master.run()
466 except TimeoutException:
468 except TimeoutException:
467 pass
469 pass
468 finally:
470 finally:
469 self.master.shutdown()
471 self.master.shutdown()
470
472
471 if 'inserve' not in sys.argv:
473 if 'inserve' not in sys.argv:
472 runargs = util.hgcmd() + ['inserve', '-R', root]
474 runargs = util.hgcmd() + ['inserve', '-R', root]
473 else:
475 else:
474 runargs = util.hgcmd() + sys.argv[1:]
476 runargs = util.hgcmd() + sys.argv[1:]
475
477
476 pidfile = ui.config('inotify', 'pidfile')
478 pidfile = ui.config('inotify', 'pidfile')
477 if opts['daemon'] and pidfile is not None and 'pid-file' not in runargs:
479 if opts['daemon'] and pidfile is not None and 'pid-file' not in runargs:
478 runargs.append("--pid-file=%s" % pidfile)
480 runargs.append("--pid-file=%s" % pidfile)
479
481
480 service = service()
482 service = service()
481 logfile = ui.config('inotify', 'log')
483 logfile = ui.config('inotify', 'log')
482
484
483 appendpid = ui.configbool('inotify', 'appendpid', False)
485 appendpid = ui.configbool('inotify', 'appendpid', False)
484
486
485 cmdutil.service(opts, initfn=service.init, runfn=service.run,
487 cmdutil.service(opts, initfn=service.init, runfn=service.run,
486 logfile=logfile, runargs=runargs, appendpid=appendpid)
488 logfile=logfile, runargs=runargs, appendpid=appendpid)
@@ -1,100 +1,113 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" inotify || exit 80
3 "$TESTDIR/hghave" inotify || exit 80
4
4
5 hg init repo1
5 hg init repo1
6 cd repo1
6 cd repo1
7
7
8 touch a b c d e
8 touch a b c d e
9 mkdir dir
9 mkdir dir
10 mkdir dir/bar
10 mkdir dir/bar
11 touch dir/x dir/y dir/bar/foo
11 touch dir/x dir/y dir/bar/foo
12
12
13 hg ci -Am m
13 hg ci -Am m
14 cd ..
14 cd ..
15 hg clone repo1 repo2
15 hg clone repo1 repo2
16
16
17 echo "[extensions]" >> $HGRCPATH
17 echo "[extensions]" >> $HGRCPATH
18 echo "inotify=" >> $HGRCPATH
18 echo "inotify=" >> $HGRCPATH
19
19
20 cd repo2
20 cd repo2
21 echo b >> a
21 echo b >> a
22 # check that daemon started automatically works correctly
22 # check that daemon started automatically works correctly
23 # and make sure that inotify.pidfile works
23 # and make sure that inotify.pidfile works
24 hg --config "inotify.pidfile=../hg2.pid" status
24 hg --config "inotify.pidfile=../hg2.pid" status
25
25
26 # make sure that pidfile worked. Output should be silent.
26 # make sure that pidfile worked. Output should be silent.
27 kill `cat ../hg2.pid`
27 kill `cat ../hg2.pid`
28
28
29 cd ../repo1
29 cd ../repo1
30 echo % inserve
30 echo % inserve
31 hg inserve -d --pid-file=hg.pid
31 hg inserve -d --pid-file=hg.pid
32 cat hg.pid >> "$DAEMON_PIDS"
32 cat hg.pid >> "$DAEMON_PIDS"
33
33
34 # let the daemon finish its stuff
34 # let the daemon finish its stuff
35 sleep 1
35 sleep 1
36
36
37 echo % cannot start, already bound
37 echo % cannot start, already bound
38 hg inserve
38 hg inserve
39
39
40 # issue907
40 # issue907
41 hg status
41 hg status
42 echo % clean
42 echo % clean
43 hg status -c
43 hg status -c
44 echo % all
44 echo % all
45 hg status -A
45 hg status -A
46
46
47 echo '% path patterns'
47 echo '% path patterns'
48 echo x > dir/x
48 echo x > dir/x
49 hg status .
49 hg status .
50 hg status dir
50 hg status dir
51 cd dir
51 cd dir
52 hg status .
52 hg status .
53 cd ..
53 cd ..
54
54
55 #issue 1375
55 #issue 1375
56 #Testing that we can remove a folder and then add a file with the same name
56 #Testing that we can remove a folder and then add a file with the same name
57 echo % issue 1375
57 echo % issue 1375
58
58
59 mkdir h
59 mkdir h
60 echo h > h/h
60 echo h > h/h
61 hg ci -Am t
61 hg ci -Am t
62 hg rm h
62 hg rm h
63
63
64 echo h >h
64 echo h >h
65 hg add h
65 hg add h
66
66
67 hg status
67 hg status
68 hg ci -m0
68 hg ci -m0
69
69
70 # Test for issue1735: inotify watches files in .hg/merge
70 # Test for issue1735: inotify watches files in .hg/merge
71 hg st
71 hg st
72
72
73 echo a > a
73 echo a > a
74
74
75 hg ci -Am a
75 hg ci -Am a
76 hg st
76 hg st
77
77
78 echo b >> a
78 echo b >> a
79 hg ci -m ab
79 hg ci -m ab
80 hg st
80 hg st
81
81
82 echo c >> a
82 echo c >> a
83 hg st
83 hg st
84
84
85 HGMERGE=internal:local hg up 0
85 HGMERGE=internal:local hg up 0
86 hg st
86 hg st
87
87
88 HGMERGE=internal:local hg up
88 HGMERGE=internal:local hg up
89 hg st
89 hg st
90
90
91 # Test for 1844: "hg ci folder" will not commit all changes beneath "folder"
91 # Test for 1844: "hg ci folder" will not commit all changes beneath "folder"
92 mkdir 1844
92 mkdir 1844
93 echo a > 1844/foo
93 echo a > 1844/foo
94 hg add 1844
94 hg add 1844
95 hg ci -m 'working'
95 hg ci -m 'working'
96
96
97 echo b >> 1844/foo
97 echo b >> 1844/foo
98 hg ci 1844 -m 'broken'
98 hg ci 1844 -m 'broken'
99
99
100 # Test for issue884: "Build products not ignored until .hgignore is touched"
101 echo '^build$' > .hgignore
102 hg add .hgignore
103 hg ci .hgignore -m 'ignorelist'
104
105 # Now, lets add some build products...
106 mkdir build
107 touch build/x
108 touch build/y
109
110 # build/x & build/y shouldn't appear in "hg st"
111 hg st
112
100 kill `cat hg.pid`
113 kill `cat hg.pid`
General Comments 0
You need to be logged in to leave comments. Login now