--- a/hgext/inotify/server.py Sun Jul 12 21:41:12 2009 +0200
+++ b/hgext/inotify/server.py Tue May 26 23:00:35 2009 +0900
@@ -201,6 +201,92 @@
return wrapper
return decorator
+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):
+ """
+ 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():
+ for e in dir.walk(states):
+ yield e
+
+ def lookup(self, states, path):
+ """
+ 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
+ return
+
+ try:
+ # if path is a directory, walk it
+ for file, st in tree.dirs[last].walk(states):
+ yield file
+ except KeyError:
+ try:
+ if tree.files[last] in states:
+ # path is a file
+ yield path
+ except KeyError:
+ # path is not tracked
+ pass
+
class repowatcher(pollable):
"""
Watches inotify events
@@ -231,9 +317,9 @@
self.threshold = watcher.threshold(self.watcher)
self.fileno = self.watcher.fileno
- self.tree = {}
+ self.tree = directory()
self.statcache = {}
- self.statustrees = dict([(s, {}) for s in self.statuskeys])
+ self.statustrees = dict([(s, directory()) for s in self.statuskeys])
self.last_event = None
@@ -288,23 +374,6 @@
self.add_watch(self.repo.path, inotify.IN_DELETE)
self.check_dirstate()
- def dir(self, tree, path):
- if path:
- for name in path.split('/'):
- tree = tree.setdefault(name, {})
- return tree
-
- def lookup(self, path, tree):
- if path:
- try:
- for name in path.split('/'):
- tree = tree[name]
- except KeyError:
- return 'x'
- except TypeError:
- return 'd'
- return tree
-
def filestatus(self, fn, st):
try:
type_, mode, size, time = self.repo.dirstate._map[fn][:4]
@@ -350,53 +419,47 @@
def _updatestatus(self, wfn, newstatus):
'''
- Update the stored status of a file or directory.
+ 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.dir(self.tree, root)
+ d = self.tree.dir(root)
- oldstatus = d.get(fn)
+ oldstatus = d.files.get(fn)
# oldstatus can be either:
# - None : fn is new
# - a char in statuskeys: fn is a (tracked) file
- # - a dict: fn is a directory
- isdir = isinstance(oldstatus, dict)
if self.ui.debugflag and oldstatus != newstatus:
- if isdir:
- self.ui.note(_('status: %r dir(%d) -> %s\n') %
- (wfn, len(oldstatus), newstatus))
- else:
- self.ui.note(_('status: %r %s -> %s\n') %
+ self.ui.note(_('status: %r %s -> %s\n') %
(wfn, oldstatus, newstatus))
- if not isdir:
- if oldstatus and oldstatus in self.statuskeys \
- and oldstatus != newstatus:
- del self.dir(self.statustrees[oldstatus], root)[fn]
- if newstatus and newstatus != 'i':
- d[fn] = newstatus
- if newstatus in self.statuskeys:
- dd = self.dir(self.statustrees[newstatus], root)
- if oldstatus != newstatus or fn not in dd:
- dd[fn] = newstatus
- else:
- d.pop(fn, None)
+
+ if oldstatus and oldstatus in self.statuskeys \
+ and oldstatus != newstatus:
+ del self.statustrees[oldstatus].dir(root).files[fn]
+ if newstatus and newstatus != 'i':
+ d.files[fn] = newstatus
+ if newstatus in self.statuskeys:
+ dd = self.statustrees[newstatus].dir(root)
+ if oldstatus != newstatus or fn not in dd.files:
+ dd.files[fn] = newstatus
+ else:
+ d.files.pop(fn, None)
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.walk(key, self.statustrees[key]):
+ for wfn, ignore in self.statustrees[key].walk(key):
if wfn not in self.repo.dirstate:
nuke.append(wfn)
for wfn in nuke:
root, fn = split(wfn)
- del self.dir(self.statustrees[key], root)[fn]
- del self.dir(self.tree, root)[fn]
+ del self.statustrees[key].dir(root).files[fn]
+ del self.tree.dir(root).files[fn]
def scan(self, topdir=''):
ds = self.repo.dirstate._map.copy()
@@ -438,18 +501,6 @@
self.scan()
self.ui.note(_('%s end dirstate reload\n') % self.event_time())
- def walk(self, states, tree, prefix=''):
- # This is the "inner loop" when talking to the client.
-
- for name, val in tree.iteritems():
- path = join(prefix, name)
- try:
- if val in states:
- yield path, val
- except TypeError:
- for p in self.walk(states, val, path):
- yield p
-
def update_hgignore(self):
# An update of the ignore file can potentially change the
# states of all unknown and ignored files.
@@ -537,9 +588,10 @@
(self.event_time(), wpath))
if evt.mask & inotify.IN_ISDIR:
- tree = self.dir(self.tree, wpath).copy()
- for wfn, ignore in self.walk('?', tree):
- self.deletefile(join(wpath, wfn), '?')
+ 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)
@@ -669,18 +721,13 @@
if not names:
def genresult(states, tree):
- for fn, state in self.repowatcher.walk(states, tree):
+ for fn, state in tree.walk(states):
yield fn
else:
def genresult(states, tree):
for fn in names:
- l = self.repowatcher.lookup(fn, tree)
- try:
- if l in states:
- yield fn
- except TypeError:
- for f, s in self.repowatcher.walk(states, l, fn):
- yield f
+ for f in tree.lookup(states, fn):
+ yield f
return ['\0'.join(r) for r in [
genresult('l', self.repowatcher.statustrees['l']),