Mercurial > hg
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 |