comparison hgext/inotify/server.py @ 9350:b789ea382fc0

inotify: server: use dirstate instead of repo
author Nicolas Dumazet <nicdumz.commits@gmail.com>
date Mon, 29 Jun 2009 01:09:33 +0900
parents 56fb15ad8fb1
children 206f7f4c5c2a
comparison
equal deleted inserted replaced
9349:56fb15ad8fb1 9350:b789ea382fc0
32 return '', path 32 return '', path
33 return path[:c], path[c+1:] 33 return path[:c], path[c+1:]
34 34
35 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG) 35 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
36 36
37 def walkrepodirs(repo): 37 def walkrepodirs(dirstate, absroot):
38 '''Iterate over all subdirectories of this repo. 38 '''Iterate over all subdirectories of this repo.
39 Exclude the .hg directory, any nested repos, and ignored dirs.''' 39 Exclude the .hg directory, any nested repos, and ignored dirs.'''
40 rootslash = repo.root + os.sep
41
42 def walkit(dirname, top): 40 def walkit(dirname, top):
43 fullpath = rootslash + dirname 41 fullpath = join(absroot, dirname)
44 try: 42 try:
45 for name, kind in osutil.listdir(fullpath): 43 for name, kind in osutil.listdir(fullpath):
46 if kind == stat.S_IFDIR: 44 if kind == stat.S_IFDIR:
47 if name == '.hg': 45 if name == '.hg':
48 if not top: 46 if not top:
49 return 47 return
50 else: 48 else:
51 d = join(dirname, name) 49 d = join(dirname, name)
52 if repo.dirstate._ignore(d): 50 if dirstate._ignore(d):
53 continue 51 continue
54 for subdir in walkit(d, False): 52 for subdir in walkit(d, False):
55 yield subdir 53 yield subdir
56 except OSError, err: 54 except OSError, err:
57 if err.errno not in walk_ignored_errors: 55 if err.errno not in walk_ignored_errors:
58 raise 56 raise
59 yield fullpath 57 yield fullpath
60 58
61 return walkit('', True) 59 return walkit('', True)
62 60
63 def walk(repo, root): 61 def walk(dirstate, absroot, root):
64 '''Like os.walk, but only yields regular files.''' 62 '''Like os.walk, but only yields regular files.'''
65 63
66 # This function is critical to performance during startup. 64 # This function is critical to performance during startup.
67
68 rootslash = repo.root + os.sep
69 65
70 def walkit(root, reporoot): 66 def walkit(root, reporoot):
71 files, dirs = [], [] 67 files, dirs = [], []
72 68
73 try: 69 try:
74 fullpath = rootslash + root 70 fullpath = join(absroot, root)
75 for name, kind in osutil.listdir(fullpath): 71 for name, kind in osutil.listdir(fullpath):
76 if kind == stat.S_IFDIR: 72 if kind == stat.S_IFDIR:
77 if name == '.hg': 73 if name == '.hg':
78 if not reporoot: 74 if not reporoot:
79 return 75 return
80 else: 76 else:
81 dirs.append(name) 77 dirs.append(name)
82 path = join(root, name) 78 path = join(root, name)
83 if repo.dirstate._ignore(path): 79 if dirstate._ignore(path):
84 continue 80 continue
85 for result in walkit(path, False): 81 for result in walkit(path, False):
86 yield result 82 yield result
87 elif kind in (stat.S_IFREG, stat.S_IFLNK): 83 elif kind in (stat.S_IFREG, stat.S_IFLNK):
88 files.append(name) 84 files.append(name)
96 elif err.errno not in walk_ignored_errors: 92 elif err.errno not in walk_ignored_errors:
97 raise 93 raise
98 94
99 return walkit(root, root == '') 95 return walkit(root, root == '')
100 96
101 def _explain_watch_limit(ui, repo): 97 def _explain_watch_limit(ui, dirstate, rootabs):
102 path = '/proc/sys/fs/inotify/max_user_watches' 98 path = '/proc/sys/fs/inotify/max_user_watches'
103 try: 99 try:
104 limit = int(file(path).read()) 100 limit = int(file(path).read())
105 except IOError, err: 101 except IOError, err:
106 if err.errno != errno.ENOENT: 102 if err.errno != errno.ENOENT:
110 ui.warn(_('*** the current per-user limit on the number ' 106 ui.warn(_('*** the current per-user limit on the number '
111 'of inotify watches is %s\n') % limit) 107 'of inotify watches is %s\n') % limit)
112 ui.warn(_('*** this limit is too low to watch every ' 108 ui.warn(_('*** this limit is too low to watch every '
113 'directory in this repository\n')) 109 'directory in this repository\n'))
114 ui.warn(_('*** counting directories: ')) 110 ui.warn(_('*** counting directories: '))
115 ndirs = len(list(walkrepodirs(repo))) 111 ndirs = len(list(walkrepodirs(dirstate, rootabs)))
116 ui.warn(_('found %d\n') % ndirs) 112 ui.warn(_('found %d\n') % ndirs)
117 newlimit = min(limit, 1024) 113 newlimit = min(limit, 1024)
118 while newlimit < ((limit + ndirs) * 1.1): 114 while newlimit < ((limit + ndirs) * 1.1):
119 newlimit *= 2 115 newlimit *= 2
120 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') % 116 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
121 (limit, newlimit)) 117 (limit, newlimit))
122 ui.warn(_('*** echo %d > %s\n') % (newlimit, path)) 118 ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
123 raise util.Abort(_('cannot watch %s until inotify watch limit is raised') 119 raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
124 % repo.root) 120 % rootabs)
125 121
126 class pollable(object): 122 class pollable(object):
127 """ 123 """
128 Interface to support polling. 124 Interface to support polling.
129 The file descriptor returned by fileno() is registered to a polling 125 The file descriptor returned by fileno() is registered to a polling
307 inotify.IN_MOVE_SELF | 303 inotify.IN_MOVE_SELF |
308 inotify.IN_ONLYDIR | 304 inotify.IN_ONLYDIR |
309 inotify.IN_UNMOUNT | 305 inotify.IN_UNMOUNT |
310 0) 306 0)
311 307
312 def __init__(self, ui, repo): 308 def __init__(self, ui, dirstate, root):
313 self.ui = ui 309 self.ui = ui
314 self.repo = repo 310 self.dirstate = dirstate
315 self.wprefix = join(repo.root, '') 311
312 self.wprefix = join(root, '')
316 self.prefixlen = len(self.wprefix) 313 self.prefixlen = len(self.wprefix)
317 try: 314 try:
318 self.watcher = watcher.watcher() 315 self.watcher = watcher.watcher()
319 except OSError, err: 316 except OSError, err:
320 raise util.Abort(_('inotify service not available: %s') % 317 raise util.Abort(_('inotify service not available: %s') %
350 return '+%.2f' % delta 347 return '+%.2f' % delta
351 return '+%.1f' % delta 348 return '+%.1f' % delta
352 349
353 def dirstate_info(self): 350 def dirstate_info(self):
354 try: 351 try:
355 st = os.lstat(self.repo.join('dirstate')) 352 st = os.lstat(self.wprefix + '.hg/dirstate')
356 return st.st_mtime, st.st_ino 353 return st.st_mtime, st.st_ino
357 except OSError, err: 354 except OSError, err:
358 if err.errno != errno.ENOENT: 355 if err.errno != errno.ENOENT:
359 raise 356 raise
360 return 0, 0 357 return 0, 0
370 except OSError, err: 367 except OSError, err:
371 if err.errno in (errno.ENOENT, errno.ENOTDIR): 368 if err.errno in (errno.ENOENT, errno.ENOTDIR):
372 return 369 return
373 if err.errno != errno.ENOSPC: 370 if err.errno != errno.ENOSPC:
374 raise 371 raise
375 _explain_watch_limit(self.ui, self.repo) 372 _explain_watch_limit(self.ui, self.dirstate, self.wprefix)
376 373
377 def setup(self): 374 def setup(self):
378 self.ui.note(_('watching directories under %r\n') % self.wprefix) 375 self.ui.note(_('watching directories under %r\n') % self.wprefix)
379 self.add_watch(self.repo.path, inotify.IN_DELETE) 376 self.add_watch(self.wprefix + '.hg', inotify.IN_DELETE)
380 self.check_dirstate() 377 self.check_dirstate()
381 378
382 def filestatus(self, fn, st): 379 def filestatus(self, fn, st):
383 try: 380 try:
384 type_, mode, size, time = self.repo.dirstate._map[fn][:4] 381 type_, mode, size, time = self.dirstate._map[fn][:4]
385 except KeyError: 382 except KeyError:
386 type_ = '?' 383 type_ = '?'
387 if type_ == 'n': 384 if type_ == 'n':
388 st_mode, st_size, st_mtime = st 385 st_mode, st_size, st_mtime = st
389 if size == -1: 386 if size == -1:
391 if size and (size != st_size or (mode ^ st_mode) & 0100): 388 if size and (size != st_size or (mode ^ st_mode) & 0100):
392 return 'm' 389 return 'm'
393 if time != int(st_mtime): 390 if time != int(st_mtime):
394 return 'l' 391 return 'l'
395 return 'n' 392 return 'n'
396 if type_ == '?' and self.repo.dirstate._ignore(fn): 393 if type_ == '?' and self.dirstate._ignore(fn):
397 return 'i' 394 return 'i'
398 return type_ 395 return type_
399 396
400 def updatefile(self, wfn, osstat): 397 def updatefile(self, wfn, osstat):
401 ''' 398 '''
456 def check_deleted(self, key): 453 def check_deleted(self, key):
457 # Files that had been deleted but were present in the dirstate 454 # Files that had been deleted but were present in the dirstate
458 # may have vanished from the dirstate; we must clean them up. 455 # may have vanished from the dirstate; we must clean them up.
459 nuke = [] 456 nuke = []
460 for wfn, ignore in self.statustrees[key].walk(key): 457 for wfn, ignore in self.statustrees[key].walk(key):
461 if wfn not in self.repo.dirstate: 458 if wfn not in self.dirstate:
462 nuke.append(wfn) 459 nuke.append(wfn)
463 for wfn in nuke: 460 for wfn in nuke:
464 root, fn = split(wfn) 461 root, fn = split(wfn)
465 del self.statustrees[key].dir(root).files[fn] 462 del self.statustrees[key].dir(root).files[fn]
466 del self.tree.dir(root).files[fn] 463 del self.tree.dir(root).files[fn]
467 464
468 def scan(self, topdir=''): 465 def scan(self, topdir=''):
469 ds = self.repo.dirstate._map.copy() 466 ds = self.dirstate._map.copy()
470 self.add_watch(join(self.wprefix, topdir), self.mask) 467 self.add_watch(join(self.wprefix, topdir), self.mask)
471 for root, dirs, files in walk(self.repo, topdir): 468 for root, dirs, files in walk(self.dirstate, self.wprefix, topdir):
472 for d in dirs: 469 for d in dirs:
473 self.add_watch(join(root, d), self.mask) 470 self.add_watch(join(root, d), self.mask)
474 wroot = root[self.prefixlen:] 471 wroot = root[self.prefixlen:]
475 for fn in files: 472 for fn in files:
476 wfn = join(wroot, fn) 473 wfn = join(wroot, fn)
498 return 495 return
499 self.ds_info = ds_info 496 self.ds_info = ds_info
500 if not self.ui.debugflag: 497 if not self.ui.debugflag:
501 self.last_event = None 498 self.last_event = None
502 self.ui.note(_('%s dirstate reload\n') % self.event_time()) 499 self.ui.note(_('%s dirstate reload\n') % self.event_time())
503 self.repo.dirstate.invalidate() 500 self.dirstate.invalidate()
504 self.handle_timeout() 501 self.handle_timeout()
505 self.scan() 502 self.scan()
506 self.ui.note(_('%s end dirstate reload\n') % self.event_time()) 503 self.ui.note(_('%s end dirstate reload\n') % self.event_time())
507 504
508 def update_hgignore(self): 505 def update_hgignore(self):
514 # potentially never see changes to them. We could get the 511 # potentially never see changes to them. We could get the
515 # client to report to us what ignore data they're using. 512 # client to report to us what ignore data they're using.
516 # But it's easier to do nothing than to open that can of 513 # But it's easier to do nothing than to open that can of
517 # worms. 514 # worms.
518 515
519 if '_ignore' in self.repo.dirstate.__dict__: 516 if '_ignore' in self.dirstate.__dict__:
520 delattr(self.repo.dirstate, '_ignore') 517 delattr(self.dirstate, '_ignore')
521 self.ui.note(_('rescanning due to .hgignore change\n')) 518 self.ui.note(_('rescanning due to .hgignore change\n'))
522 self.handle_timeout() 519 self.handle_timeout()
523 self.scan() 520 self.scan()
524 521
525 def getstat(self, wpath): 522 def getstat(self, wpath):
558 if wpath == '.hgignore': 555 if wpath == '.hgignore':
559 self.update_hgignore() 556 self.update_hgignore()
560 try: 557 try:
561 st = self.stat(wpath) 558 st = self.stat(wpath)
562 if stat.S_ISREG(st[0]): 559 if stat.S_ISREG(st[0]):
563 if self.repo.dirstate[wpath] in 'lmn': 560 if self.dirstate[wpath] in 'lmn':
564 self.updatefile(wpath, st) 561 self.updatefile(wpath, st)
565 except OSError: 562 except OSError:
566 pass 563 pass
567 564
568 @eventaction('d') 565 @eventaction('d')
572 elif wpath.startswith('.hg/'): 569 elif wpath.startswith('.hg/'):
573 if wpath == '.hg/wlock': 570 if wpath == '.hg/wlock':
574 self.check_dirstate() 571 self.check_dirstate()
575 return 572 return
576 573
577 self.deletefile(wpath, self.repo.dirstate[wpath]) 574 self.deletefile(wpath, self.dirstate[wpath])
578 575
579 def process_create(self, wpath, evt): 576 def process_create(self, wpath, evt):
580 if self.ui.debugflag: 577 if self.ui.debugflag:
581 self.ui.note(_('%s event: created %s\n') % 578 self.ui.note(_('%s event: created %s\n') %
582 (self.event_time(), wpath)) 579 (self.event_time(), wpath))
676 673
677 class server(pollable): 674 class server(pollable):
678 """ 675 """
679 Listens for client queries on unix socket inotify.sock 676 Listens for client queries on unix socket inotify.sock
680 """ 677 """
681 def __init__(self, ui, repo, repowatcher, timeout): 678 def __init__(self, ui, root, repowatcher, timeout):
682 self.ui = ui 679 self.ui = ui
683 self.repo = repo
684 self.repowatcher = repowatcher 680 self.repowatcher = repowatcher
685 self.sock = socket.socket(socket.AF_UNIX) 681 self.sock = socket.socket(socket.AF_UNIX)
686 self.sockpath = self.repo.join('inotify.sock') 682 self.sockpath = join(root, '.hg/inotify.sock')
687 self.realsockpath = None 683 self.realsockpath = None
688 try: 684 try:
689 self.sock.bind(self.sockpath) 685 self.sock.bind(self.sockpath)
690 except socket.error, err: 686 except socket.error, err:
691 if err[0] == errno.EADDRINUSE: 687 if err[0] == errno.EADDRINUSE:
811 raise 807 raise
812 808
813 class master(object): 809 class master(object):
814 def __init__(self, ui, repo, timeout=None): 810 def __init__(self, ui, repo, timeout=None):
815 self.ui = ui 811 self.ui = ui
816 self.repo = repo 812 self.repowatcher = repowatcher(ui, repo.dirstate, repo.root)
817 self.repowatcher = repowatcher(ui, repo) 813 self.server = server(ui, repo.root, self.repowatcher, timeout)
818 self.server = server(ui, repo, self.repowatcher, timeout)
819 814
820 def shutdown(self): 815 def shutdown(self):
821 for obj in pollable.instances.itervalues(): 816 for obj in pollable.instances.itervalues():
822 obj.shutdown() 817 obj.shutdown()
823 818