|
@@
-1,717
+1,738
b''
|
|
1
|
# server.py - inotify status server
|
|
1
|
# server.py - inotify status server
|
|
2
|
#
|
|
2
|
#
|
|
3
|
# Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
|
|
3
|
# Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
|
|
4
|
# Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
|
|
4
|
# Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
|
|
5
|
#
|
|
5
|
#
|
|
6
|
# This software may be used and distributed according to the terms
|
|
6
|
# This software may be used and distributed according to the terms
|
|
7
|
# of the GNU General Public License, incorporated herein by reference.
|
|
7
|
# of the GNU General Public License, incorporated herein by reference.
|
|
8
|
|
|
8
|
|
|
9
|
from mercurial.i18n import gettext as _
|
|
9
|
from mercurial.i18n import gettext as _
|
|
10
|
from mercurial import osutil, ui, util
|
|
10
|
from mercurial import osutil, ui, util
|
|
11
|
import common
|
|
11
|
import common
|
|
12
|
import errno, os, select, socket, stat, struct, sys, time
|
|
12
|
import errno, os, select, socket, stat, struct, sys, tempfile, time
|
|
13
|
|
|
13
|
|
|
14
|
try:
|
|
14
|
try:
|
|
15
|
import linux as inotify
|
|
15
|
import linux as inotify
|
|
16
|
from linux import watcher
|
|
16
|
from linux import watcher
|
|
17
|
except ImportError:
|
|
17
|
except ImportError:
|
|
18
|
print >> sys.stderr, '*** native support is required for this extension'
|
|
18
|
print >> sys.stderr, '*** native support is required for this extension'
|
|
19
|
raise
|
|
19
|
raise
|
|
20
|
|
|
20
|
|
|
21
|
class AlreadyStartedException(Exception): pass
|
|
21
|
class AlreadyStartedException(Exception): pass
|
|
22
|
|
|
22
|
|
|
23
|
def join(a, b):
|
|
23
|
def join(a, b):
|
|
24
|
if a:
|
|
24
|
if a:
|
|
25
|
if a[-1] == '/':
|
|
25
|
if a[-1] == '/':
|
|
26
|
return a + b
|
|
26
|
return a + b
|
|
27
|
return a + '/' + b
|
|
27
|
return a + '/' + b
|
|
28
|
return b
|
|
28
|
return b
|
|
29
|
|
|
29
|
|
|
30
|
walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
|
|
30
|
walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
|
|
31
|
|
|
31
|
|
|
32
|
def walkrepodirs(repo):
|
|
32
|
def walkrepodirs(repo):
|
|
33
|
'''Iterate over all subdirectories of this repo.
|
|
33
|
'''Iterate over all subdirectories of this repo.
|
|
34
|
Exclude the .hg directory, any nested repos, and ignored dirs.'''
|
|
34
|
Exclude the .hg directory, any nested repos, and ignored dirs.'''
|
|
35
|
rootslash = repo.root + os.sep
|
|
35
|
rootslash = repo.root + os.sep
|
|
36
|
def walkit(dirname, top):
|
|
36
|
def walkit(dirname, top):
|
|
37
|
hginside = False
|
|
37
|
hginside = False
|
|
38
|
try:
|
|
38
|
try:
|
|
39
|
for name, kind in osutil.listdir(rootslash + dirname):
|
|
39
|
for name, kind in osutil.listdir(rootslash + dirname):
|
|
40
|
if kind == stat.S_IFDIR:
|
|
40
|
if kind == stat.S_IFDIR:
|
|
41
|
if name == '.hg':
|
|
41
|
if name == '.hg':
|
|
42
|
hginside = True
|
|
42
|
hginside = True
|
|
43
|
if not top: break
|
|
43
|
if not top: break
|
|
44
|
else:
|
|
44
|
else:
|
|
45
|
d = join(dirname, name)
|
|
45
|
d = join(dirname, name)
|
|
46
|
if repo.dirstate._ignore(d):
|
|
46
|
if repo.dirstate._ignore(d):
|
|
47
|
continue
|
|
47
|
continue
|
|
48
|
for subdir, hginsub in walkit(d, False):
|
|
48
|
for subdir, hginsub in walkit(d, False):
|
|
49
|
if not hginsub:
|
|
49
|
if not hginsub:
|
|
50
|
yield subdir, False
|
|
50
|
yield subdir, False
|
|
51
|
except OSError, err:
|
|
51
|
except OSError, err:
|
|
52
|
if err.errno not in walk_ignored_errors:
|
|
52
|
if err.errno not in walk_ignored_errors:
|
|
53
|
raise
|
|
53
|
raise
|
|
54
|
yield rootslash + dirname, hginside
|
|
54
|
yield rootslash + dirname, hginside
|
|
55
|
for dirname, hginside in walkit('', True):
|
|
55
|
for dirname, hginside in walkit('', True):
|
|
56
|
yield dirname
|
|
56
|
yield dirname
|
|
57
|
|
|
57
|
|
|
58
|
def walk(repo, root):
|
|
58
|
def walk(repo, root):
|
|
59
|
'''Like os.walk, but only yields regular files.'''
|
|
59
|
'''Like os.walk, but only yields regular files.'''
|
|
60
|
|
|
60
|
|
|
61
|
# This function is critical to performance during startup.
|
|
61
|
# This function is critical to performance during startup.
|
|
62
|
|
|
62
|
|
|
63
|
reporoot = root == ''
|
|
63
|
reporoot = root == ''
|
|
64
|
rootslash = repo.root + os.sep
|
|
64
|
rootslash = repo.root + os.sep
|
|
65
|
|
|
65
|
|
|
66
|
def walkit(root, reporoot):
|
|
66
|
def walkit(root, reporoot):
|
|
67
|
files, dirs = [], []
|
|
67
|
files, dirs = [], []
|
|
68
|
hginside = False
|
|
68
|
hginside = False
|
|
69
|
|
|
69
|
|
|
70
|
try:
|
|
70
|
try:
|
|
71
|
fullpath = rootslash + root
|
|
71
|
fullpath = rootslash + root
|
|
72
|
for name, kind in osutil.listdir(fullpath):
|
|
72
|
for name, kind in osutil.listdir(fullpath):
|
|
73
|
if kind == stat.S_IFDIR:
|
|
73
|
if kind == stat.S_IFDIR:
|
|
74
|
if name == '.hg':
|
|
74
|
if name == '.hg':
|
|
75
|
hginside = True
|
|
75
|
hginside = True
|
|
76
|
if reporoot:
|
|
76
|
if reporoot:
|
|
77
|
continue
|
|
77
|
continue
|
|
78
|
else:
|
|
78
|
else:
|
|
79
|
break
|
|
79
|
break
|
|
80
|
dirs.append(name)
|
|
80
|
dirs.append(name)
|
|
81
|
elif kind in (stat.S_IFREG, stat.S_IFLNK):
|
|
81
|
elif kind in (stat.S_IFREG, stat.S_IFLNK):
|
|
82
|
path = join(root, name)
|
|
82
|
path = join(root, name)
|
|
83
|
files.append((name, kind))
|
|
83
|
files.append((name, kind))
|
|
84
|
|
|
84
|
|
|
85
|
yield hginside, fullpath, dirs, files
|
|
85
|
yield hginside, fullpath, dirs, files
|
|
86
|
|
|
86
|
|
|
87
|
for subdir in dirs:
|
|
87
|
for subdir in dirs:
|
|
88
|
path = join(root, subdir)
|
|
88
|
path = join(root, subdir)
|
|
89
|
if repo.dirstate._ignore(path):
|
|
89
|
if repo.dirstate._ignore(path):
|
|
90
|
continue
|
|
90
|
continue
|
|
91
|
for result in walkit(path, False):
|
|
91
|
for result in walkit(path, False):
|
|
92
|
if not result[0]:
|
|
92
|
if not result[0]:
|
|
93
|
yield result
|
|
93
|
yield result
|
|
94
|
except OSError, err:
|
|
94
|
except OSError, err:
|
|
95
|
if err.errno not in walk_ignored_errors:
|
|
95
|
if err.errno not in walk_ignored_errors:
|
|
96
|
raise
|
|
96
|
raise
|
|
97
|
for result in walkit(root, reporoot):
|
|
97
|
for result in walkit(root, reporoot):
|
|
98
|
yield result[1:]
|
|
98
|
yield result[1:]
|
|
99
|
|
|
99
|
|
|
100
|
def _explain_watch_limit(ui, repo, count):
|
|
100
|
def _explain_watch_limit(ui, repo, count):
|
|
101
|
path = '/proc/sys/fs/inotify/max_user_watches'
|
|
101
|
path = '/proc/sys/fs/inotify/max_user_watches'
|
|
102
|
try:
|
|
102
|
try:
|
|
103
|
limit = int(file(path).read())
|
|
103
|
limit = int(file(path).read())
|
|
104
|
except IOError, err:
|
|
104
|
except IOError, err:
|
|
105
|
if err.errno != errno.ENOENT:
|
|
105
|
if err.errno != errno.ENOENT:
|
|
106
|
raise
|
|
106
|
raise
|
|
107
|
raise util.Abort(_('this system does not seem to '
|
|
107
|
raise util.Abort(_('this system does not seem to '
|
|
108
|
'support inotify'))
|
|
108
|
'support inotify'))
|
|
109
|
ui.warn(_('*** the current per-user limit on the number '
|
|
109
|
ui.warn(_('*** the current per-user limit on the number '
|
|
110
|
'of inotify watches is %s\n') % limit)
|
|
110
|
'of inotify watches is %s\n') % limit)
|
|
111
|
ui.warn(_('*** this limit is too low to watch every '
|
|
111
|
ui.warn(_('*** this limit is too low to watch every '
|
|
112
|
'directory in this repository\n'))
|
|
112
|
'directory in this repository\n'))
|
|
113
|
ui.warn(_('*** counting directories: '))
|
|
113
|
ui.warn(_('*** counting directories: '))
|
|
114
|
ndirs = len(list(walkrepodirs(repo)))
|
|
114
|
ndirs = len(list(walkrepodirs(repo)))
|
|
115
|
ui.warn(_('found %d\n') % ndirs)
|
|
115
|
ui.warn(_('found %d\n') % ndirs)
|
|
116
|
newlimit = min(limit, 1024)
|
|
116
|
newlimit = min(limit, 1024)
|
|
117
|
while newlimit < ((limit + ndirs) * 1.1):
|
|
117
|
while newlimit < ((limit + ndirs) * 1.1):
|
|
118
|
newlimit *= 2
|
|
118
|
newlimit *= 2
|
|
119
|
ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
|
|
119
|
ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
|
|
120
|
(limit, newlimit))
|
|
120
|
(limit, newlimit))
|
|
121
|
ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
|
|
121
|
ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
|
|
122
|
raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
|
|
122
|
raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
|
|
123
|
% repo.root)
|
|
123
|
% repo.root)
|
|
124
|
|
|
124
|
|
|
125
|
class Watcher(object):
|
|
125
|
class Watcher(object):
|
|
126
|
poll_events = select.POLLIN
|
|
126
|
poll_events = select.POLLIN
|
|
127
|
statuskeys = 'almr!?'
|
|
127
|
statuskeys = 'almr!?'
|
|
128
|
|
|
128
|
|
|
129
|
def __init__(self, ui, repo, master):
|
|
129
|
def __init__(self, ui, repo, master):
|
|
130
|
self.ui = ui
|
|
130
|
self.ui = ui
|
|
131
|
self.repo = repo
|
|
131
|
self.repo = repo
|
|
132
|
self.wprefix = self.repo.wjoin('')
|
|
132
|
self.wprefix = self.repo.wjoin('')
|
|
133
|
self.timeout = None
|
|
133
|
self.timeout = None
|
|
134
|
self.master = master
|
|
134
|
self.master = master
|
|
135
|
self.mask = (
|
|
135
|
self.mask = (
|
|
136
|
inotify.IN_ATTRIB |
|
|
136
|
inotify.IN_ATTRIB |
|
|
137
|
inotify.IN_CREATE |
|
|
137
|
inotify.IN_CREATE |
|
|
138
|
inotify.IN_DELETE |
|
|
138
|
inotify.IN_DELETE |
|
|
139
|
inotify.IN_DELETE_SELF |
|
|
139
|
inotify.IN_DELETE_SELF |
|
|
140
|
inotify.IN_MODIFY |
|
|
140
|
inotify.IN_MODIFY |
|
|
141
|
inotify.IN_MOVED_FROM |
|
|
141
|
inotify.IN_MOVED_FROM |
|
|
142
|
inotify.IN_MOVED_TO |
|
|
142
|
inotify.IN_MOVED_TO |
|
|
143
|
inotify.IN_MOVE_SELF |
|
|
143
|
inotify.IN_MOVE_SELF |
|
|
144
|
inotify.IN_ONLYDIR |
|
|
144
|
inotify.IN_ONLYDIR |
|
|
145
|
inotify.IN_UNMOUNT |
|
|
145
|
inotify.IN_UNMOUNT |
|
|
146
|
0)
|
|
146
|
0)
|
|
147
|
try:
|
|
147
|
try:
|
|
148
|
self.watcher = watcher.Watcher()
|
|
148
|
self.watcher = watcher.Watcher()
|
|
149
|
except OSError, err:
|
|
149
|
except OSError, err:
|
|
150
|
raise util.Abort(_('inotify service not available: %s') %
|
|
150
|
raise util.Abort(_('inotify service not available: %s') %
|
|
151
|
err.strerror)
|
|
151
|
err.strerror)
|
|
152
|
self.threshold = watcher.Threshold(self.watcher)
|
|
152
|
self.threshold = watcher.Threshold(self.watcher)
|
|
153
|
self.registered = True
|
|
153
|
self.registered = True
|
|
154
|
self.fileno = self.watcher.fileno
|
|
154
|
self.fileno = self.watcher.fileno
|
|
155
|
|
|
155
|
|
|
156
|
self.repo.dirstate.__class__.inotifyserver = True
|
|
156
|
self.repo.dirstate.__class__.inotifyserver = True
|
|
157
|
|
|
157
|
|
|
158
|
self.tree = {}
|
|
158
|
self.tree = {}
|
|
159
|
self.statcache = {}
|
|
159
|
self.statcache = {}
|
|
160
|
self.statustrees = dict([(s, {}) for s in self.statuskeys])
|
|
160
|
self.statustrees = dict([(s, {}) for s in self.statuskeys])
|
|
161
|
|
|
161
|
|
|
162
|
self.watches = 0
|
|
162
|
self.watches = 0
|
|
163
|
self.last_event = None
|
|
163
|
self.last_event = None
|
|
164
|
|
|
164
|
|
|
165
|
self.eventq = {}
|
|
165
|
self.eventq = {}
|
|
166
|
self.deferred = 0
|
|
166
|
self.deferred = 0
|
|
167
|
|
|
167
|
|
|
168
|
self.ds_info = self.dirstate_info()
|
|
168
|
self.ds_info = self.dirstate_info()
|
|
169
|
self.scan()
|
|
169
|
self.scan()
|
|
170
|
|
|
170
|
|
|
171
|
def event_time(self):
|
|
171
|
def event_time(self):
|
|
172
|
last = self.last_event
|
|
172
|
last = self.last_event
|
|
173
|
now = time.time()
|
|
173
|
now = time.time()
|
|
174
|
self.last_event = now
|
|
174
|
self.last_event = now
|
|
175
|
|
|
175
|
|
|
176
|
if last is None:
|
|
176
|
if last is None:
|
|
177
|
return 'start'
|
|
177
|
return 'start'
|
|
178
|
delta = now - last
|
|
178
|
delta = now - last
|
|
179
|
if delta < 5:
|
|
179
|
if delta < 5:
|
|
180
|
return '+%.3f' % delta
|
|
180
|
return '+%.3f' % delta
|
|
181
|
if delta < 50:
|
|
181
|
if delta < 50:
|
|
182
|
return '+%.2f' % delta
|
|
182
|
return '+%.2f' % delta
|
|
183
|
return '+%.1f' % delta
|
|
183
|
return '+%.1f' % delta
|
|
184
|
|
|
184
|
|
|
185
|
def dirstate_info(self):
|
|
185
|
def dirstate_info(self):
|
|
186
|
try:
|
|
186
|
try:
|
|
187
|
st = os.lstat(self.repo.join('dirstate'))
|
|
187
|
st = os.lstat(self.repo.join('dirstate'))
|
|
188
|
return st.st_mtime, st.st_ino
|
|
188
|
return st.st_mtime, st.st_ino
|
|
189
|
except OSError, err:
|
|
189
|
except OSError, err:
|
|
190
|
if err.errno != errno.ENOENT:
|
|
190
|
if err.errno != errno.ENOENT:
|
|
191
|
raise
|
|
191
|
raise
|
|
192
|
return 0, 0
|
|
192
|
return 0, 0
|
|
193
|
|
|
193
|
|
|
194
|
def add_watch(self, path, mask):
|
|
194
|
def add_watch(self, path, mask):
|
|
195
|
if not path:
|
|
195
|
if not path:
|
|
196
|
return
|
|
196
|
return
|
|
197
|
if self.watcher.path(path) is None:
|
|
197
|
if self.watcher.path(path) is None:
|
|
198
|
if self.ui.debugflag:
|
|
198
|
if self.ui.debugflag:
|
|
199
|
self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
|
|
199
|
self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
|
|
200
|
try:
|
|
200
|
try:
|
|
201
|
self.watcher.add(path, mask)
|
|
201
|
self.watcher.add(path, mask)
|
|
202
|
self.watches += 1
|
|
202
|
self.watches += 1
|
|
203
|
except OSError, err:
|
|
203
|
except OSError, err:
|
|
204
|
if err.errno in (errno.ENOENT, errno.ENOTDIR):
|
|
204
|
if err.errno in (errno.ENOENT, errno.ENOTDIR):
|
|
205
|
return
|
|
205
|
return
|
|
206
|
if err.errno != errno.ENOSPC:
|
|
206
|
if err.errno != errno.ENOSPC:
|
|
207
|
raise
|
|
207
|
raise
|
|
208
|
_explain_watch_limit(self.ui, self.repo, self.watches)
|
|
208
|
_explain_watch_limit(self.ui, self.repo, self.watches)
|
|
209
|
|
|
209
|
|
|
210
|
def setup(self):
|
|
210
|
def setup(self):
|
|
211
|
self.ui.note(_('watching directories under %r\n') % self.repo.root)
|
|
211
|
self.ui.note(_('watching directories under %r\n') % self.repo.root)
|
|
212
|
self.add_watch(self.repo.path, inotify.IN_DELETE)
|
|
212
|
self.add_watch(self.repo.path, inotify.IN_DELETE)
|
|
213
|
self.check_dirstate()
|
|
213
|
self.check_dirstate()
|
|
214
|
|
|
214
|
|
|
215
|
def wpath(self, evt):
|
|
215
|
def wpath(self, evt):
|
|
216
|
path = evt.fullpath
|
|
216
|
path = evt.fullpath
|
|
217
|
if path == self.repo.root:
|
|
217
|
if path == self.repo.root:
|
|
218
|
return ''
|
|
218
|
return ''
|
|
219
|
if path.startswith(self.wprefix):
|
|
219
|
if path.startswith(self.wprefix):
|
|
220
|
return path[len(self.wprefix):]
|
|
220
|
return path[len(self.wprefix):]
|
|
221
|
raise 'wtf? ' + path
|
|
221
|
raise 'wtf? ' + path
|
|
222
|
|
|
222
|
|
|
223
|
def dir(self, tree, path):
|
|
223
|
def dir(self, tree, path):
|
|
224
|
if path:
|
|
224
|
if path:
|
|
225
|
for name in path.split('/'):
|
|
225
|
for name in path.split('/'):
|
|
226
|
tree.setdefault(name, {})
|
|
226
|
tree.setdefault(name, {})
|
|
227
|
tree = tree[name]
|
|
227
|
tree = tree[name]
|
|
228
|
return tree
|
|
228
|
return tree
|
|
229
|
|
|
229
|
|
|
230
|
def lookup(self, path, tree):
|
|
230
|
def lookup(self, path, tree):
|
|
231
|
if path:
|
|
231
|
if path:
|
|
232
|
try:
|
|
232
|
try:
|
|
233
|
for name in path.split('/'):
|
|
233
|
for name in path.split('/'):
|
|
234
|
tree = tree[name]
|
|
234
|
tree = tree[name]
|
|
235
|
except KeyError:
|
|
235
|
except KeyError:
|
|
236
|
return 'x'
|
|
236
|
return 'x'
|
|
237
|
except TypeError:
|
|
237
|
except TypeError:
|
|
238
|
return 'd'
|
|
238
|
return 'd'
|
|
239
|
return tree
|
|
239
|
return tree
|
|
240
|
|
|
240
|
|
|
241
|
def split(self, path):
|
|
241
|
def split(self, path):
|
|
242
|
c = path.rfind('/')
|
|
242
|
c = path.rfind('/')
|
|
243
|
if c == -1:
|
|
243
|
if c == -1:
|
|
244
|
return '', path
|
|
244
|
return '', path
|
|
245
|
return path[:c], path[c+1:]
|
|
245
|
return path[:c], path[c+1:]
|
|
246
|
|
|
246
|
|
|
247
|
def filestatus(self, fn, st):
|
|
247
|
def filestatus(self, fn, st):
|
|
248
|
try:
|
|
248
|
try:
|
|
249
|
type_, mode, size, time = self.repo.dirstate._map[fn][:4]
|
|
249
|
type_, mode, size, time = self.repo.dirstate._map[fn][:4]
|
|
250
|
except KeyError:
|
|
250
|
except KeyError:
|
|
251
|
type_ = '?'
|
|
251
|
type_ = '?'
|
|
252
|
if type_ == 'n':
|
|
252
|
if type_ == 'n':
|
|
253
|
if not st:
|
|
253
|
if not st:
|
|
254
|
return '!'
|
|
254
|
return '!'
|
|
255
|
st_mode, st_size, st_mtime = st
|
|
255
|
st_mode, st_size, st_mtime = st
|
|
256
|
if size and (size != st_size or (mode ^ st_mode) & 0100):
|
|
256
|
if size and (size != st_size or (mode ^ st_mode) & 0100):
|
|
257
|
return 'm'
|
|
257
|
return 'm'
|
|
258
|
if time != int(st_mtime):
|
|
258
|
if time != int(st_mtime):
|
|
259
|
return 'l'
|
|
259
|
return 'l'
|
|
260
|
return 'n'
|
|
260
|
return 'n'
|
|
261
|
if type_ in 'ma' and not st:
|
|
261
|
if type_ in 'ma' and not st:
|
|
262
|
return '!'
|
|
262
|
return '!'
|
|
263
|
if type_ == '?' and self.repo.dirstate._ignore(fn):
|
|
263
|
if type_ == '?' and self.repo.dirstate._ignore(fn):
|
|
264
|
return 'i'
|
|
264
|
return 'i'
|
|
265
|
return type_
|
|
265
|
return type_
|
|
266
|
|
|
266
|
|
|
267
|
def updatestatus(self, wfn, st=None, status=None, oldstatus=None):
|
|
267
|
def updatestatus(self, wfn, st=None, status=None, oldstatus=None):
|
|
268
|
if st:
|
|
268
|
if st:
|
|
269
|
status = self.filestatus(wfn, st)
|
|
269
|
status = self.filestatus(wfn, st)
|
|
270
|
else:
|
|
270
|
else:
|
|
271
|
self.statcache.pop(wfn, None)
|
|
271
|
self.statcache.pop(wfn, None)
|
|
272
|
root, fn = self.split(wfn)
|
|
272
|
root, fn = self.split(wfn)
|
|
273
|
d = self.dir(self.tree, root)
|
|
273
|
d = self.dir(self.tree, root)
|
|
274
|
if oldstatus is None:
|
|
274
|
if oldstatus is None:
|
|
275
|
oldstatus = d.get(fn)
|
|
275
|
oldstatus = d.get(fn)
|
|
276
|
isdir = False
|
|
276
|
isdir = False
|
|
277
|
if oldstatus:
|
|
277
|
if oldstatus:
|
|
278
|
try:
|
|
278
|
try:
|
|
279
|
if not status:
|
|
279
|
if not status:
|
|
280
|
if oldstatus in 'almn':
|
|
280
|
if oldstatus in 'almn':
|
|
281
|
status = '!'
|
|
281
|
status = '!'
|
|
282
|
elif oldstatus == 'r':
|
|
282
|
elif oldstatus == 'r':
|
|
283
|
status = 'r'
|
|
283
|
status = 'r'
|
|
284
|
except TypeError:
|
|
284
|
except TypeError:
|
|
285
|
# oldstatus may be a dict left behind by a deleted
|
|
285
|
# oldstatus may be a dict left behind by a deleted
|
|
286
|
# directory
|
|
286
|
# directory
|
|
287
|
isdir = True
|
|
287
|
isdir = True
|
|
288
|
else:
|
|
288
|
else:
|
|
289
|
if oldstatus in self.statuskeys and oldstatus != status:
|
|
289
|
if oldstatus in self.statuskeys and oldstatus != status:
|
|
290
|
del self.dir(self.statustrees[oldstatus], root)[fn]
|
|
290
|
del self.dir(self.statustrees[oldstatus], root)[fn]
|
|
291
|
if self.ui.debugflag and oldstatus != status:
|
|
291
|
if self.ui.debugflag and oldstatus != status:
|
|
292
|
if isdir:
|
|
292
|
if isdir:
|
|
293
|
self.ui.note('status: %r dir(%d) -> %s\n' %
|
|
293
|
self.ui.note('status: %r dir(%d) -> %s\n' %
|
|
294
|
(wfn, len(oldstatus), status))
|
|
294
|
(wfn, len(oldstatus), status))
|
|
295
|
else:
|
|
295
|
else:
|
|
296
|
self.ui.note('status: %r %s -> %s\n' %
|
|
296
|
self.ui.note('status: %r %s -> %s\n' %
|
|
297
|
(wfn, oldstatus, status))
|
|
297
|
(wfn, oldstatus, status))
|
|
298
|
if not isdir:
|
|
298
|
if not isdir:
|
|
299
|
if status and status != 'i':
|
|
299
|
if status and status != 'i':
|
|
300
|
d[fn] = status
|
|
300
|
d[fn] = status
|
|
301
|
if status in self.statuskeys:
|
|
301
|
if status in self.statuskeys:
|
|
302
|
dd = self.dir(self.statustrees[status], root)
|
|
302
|
dd = self.dir(self.statustrees[status], root)
|
|
303
|
if oldstatus != status or fn not in dd:
|
|
303
|
if oldstatus != status or fn not in dd:
|
|
304
|
dd[fn] = status
|
|
304
|
dd[fn] = status
|
|
305
|
else:
|
|
305
|
else:
|
|
306
|
d.pop(fn, None)
|
|
306
|
d.pop(fn, None)
|
|
307
|
|
|
307
|
|
|
308
|
def check_deleted(self, key):
|
|
308
|
def check_deleted(self, key):
|
|
309
|
# Files that had been deleted but were present in the dirstate
|
|
309
|
# Files that had been deleted but were present in the dirstate
|
|
310
|
# may have vanished from the dirstate; we must clean them up.
|
|
310
|
# may have vanished from the dirstate; we must clean them up.
|
|
311
|
nuke = []
|
|
311
|
nuke = []
|
|
312
|
for wfn, ignore in self.walk(key, self.statustrees[key]):
|
|
312
|
for wfn, ignore in self.walk(key, self.statustrees[key]):
|
|
313
|
if wfn not in self.repo.dirstate:
|
|
313
|
if wfn not in self.repo.dirstate:
|
|
314
|
nuke.append(wfn)
|
|
314
|
nuke.append(wfn)
|
|
315
|
for wfn in nuke:
|
|
315
|
for wfn in nuke:
|
|
316
|
root, fn = self.split(wfn)
|
|
316
|
root, fn = self.split(wfn)
|
|
317
|
del self.dir(self.statustrees[key], root)[fn]
|
|
317
|
del self.dir(self.statustrees[key], root)[fn]
|
|
318
|
del self.dir(self.tree, root)[fn]
|
|
318
|
del self.dir(self.tree, root)[fn]
|
|
319
|
|
|
319
|
|
|
320
|
def scan(self, topdir=''):
|
|
320
|
def scan(self, topdir=''):
|
|
321
|
self.handle_timeout()
|
|
321
|
self.handle_timeout()
|
|
322
|
ds = self.repo.dirstate._map.copy()
|
|
322
|
ds = self.repo.dirstate._map.copy()
|
|
323
|
self.add_watch(join(self.repo.root, topdir), self.mask)
|
|
323
|
self.add_watch(join(self.repo.root, topdir), self.mask)
|
|
324
|
for root, dirs, entries in walk(self.repo, topdir):
|
|
324
|
for root, dirs, entries in walk(self.repo, topdir):
|
|
325
|
for d in dirs:
|
|
325
|
for d in dirs:
|
|
326
|
self.add_watch(join(root, d), self.mask)
|
|
326
|
self.add_watch(join(root, d), self.mask)
|
|
327
|
wroot = root[len(self.wprefix):]
|
|
327
|
wroot = root[len(self.wprefix):]
|
|
328
|
d = self.dir(self.tree, wroot)
|
|
328
|
d = self.dir(self.tree, wroot)
|
|
329
|
for fn, kind in entries:
|
|
329
|
for fn, kind in entries:
|
|
330
|
wfn = join(wroot, fn)
|
|
330
|
wfn = join(wroot, fn)
|
|
331
|
self.updatestatus(wfn, self.getstat(wfn))
|
|
331
|
self.updatestatus(wfn, self.getstat(wfn))
|
|
332
|
ds.pop(wfn, None)
|
|
332
|
ds.pop(wfn, None)
|
|
333
|
wtopdir = topdir
|
|
333
|
wtopdir = topdir
|
|
334
|
if wtopdir and wtopdir[-1] != '/':
|
|
334
|
if wtopdir and wtopdir[-1] != '/':
|
|
335
|
wtopdir += '/'
|
|
335
|
wtopdir += '/'
|
|
336
|
for wfn, state in ds.iteritems():
|
|
336
|
for wfn, state in ds.iteritems():
|
|
337
|
if not wfn.startswith(wtopdir):
|
|
337
|
if not wfn.startswith(wtopdir):
|
|
338
|
continue
|
|
338
|
continue
|
|
339
|
status = state[0]
|
|
339
|
status = state[0]
|
|
340
|
st = self.getstat(wfn)
|
|
340
|
st = self.getstat(wfn)
|
|
341
|
if status == 'r' and not st:
|
|
341
|
if status == 'r' and not st:
|
|
342
|
self.updatestatus(wfn, st, status=status)
|
|
342
|
self.updatestatus(wfn, st, status=status)
|
|
343
|
else:
|
|
343
|
else:
|
|
344
|
self.updatestatus(wfn, st, oldstatus=status)
|
|
344
|
self.updatestatus(wfn, st, oldstatus=status)
|
|
345
|
self.check_deleted('!')
|
|
345
|
self.check_deleted('!')
|
|
346
|
self.check_deleted('r')
|
|
346
|
self.check_deleted('r')
|
|
347
|
|
|
347
|
|
|
348
|
def check_dirstate(self):
|
|
348
|
def check_dirstate(self):
|
|
349
|
ds_info = self.dirstate_info()
|
|
349
|
ds_info = self.dirstate_info()
|
|
350
|
if ds_info == self.ds_info:
|
|
350
|
if ds_info == self.ds_info:
|
|
351
|
return
|
|
351
|
return
|
|
352
|
self.ds_info = ds_info
|
|
352
|
self.ds_info = ds_info
|
|
353
|
if not self.ui.debugflag:
|
|
353
|
if not self.ui.debugflag:
|
|
354
|
self.last_event = None
|
|
354
|
self.last_event = None
|
|
355
|
self.ui.note(_('%s dirstate reload\n') % self.event_time())
|
|
355
|
self.ui.note(_('%s dirstate reload\n') % self.event_time())
|
|
356
|
self.repo.dirstate.invalidate()
|
|
356
|
self.repo.dirstate.invalidate()
|
|
357
|
self.scan()
|
|
357
|
self.scan()
|
|
358
|
self.ui.note(_('%s end dirstate reload\n') % self.event_time())
|
|
358
|
self.ui.note(_('%s end dirstate reload\n') % self.event_time())
|
|
359
|
|
|
359
|
|
|
360
|
def walk(self, states, tree, prefix=''):
|
|
360
|
def walk(self, states, tree, prefix=''):
|
|
361
|
# This is the "inner loop" when talking to the client.
|
|
361
|
# This is the "inner loop" when talking to the client.
|
|
362
|
|
|
362
|
|
|
363
|
for name, val in tree.iteritems():
|
|
363
|
for name, val in tree.iteritems():
|
|
364
|
path = join(prefix, name)
|
|
364
|
path = join(prefix, name)
|
|
365
|
try:
|
|
365
|
try:
|
|
366
|
if val in states:
|
|
366
|
if val in states:
|
|
367
|
yield path, val
|
|
367
|
yield path, val
|
|
368
|
except TypeError:
|
|
368
|
except TypeError:
|
|
369
|
for p in self.walk(states, val, path):
|
|
369
|
for p in self.walk(states, val, path):
|
|
370
|
yield p
|
|
370
|
yield p
|
|
371
|
|
|
371
|
|
|
372
|
def update_hgignore(self):
|
|
372
|
def update_hgignore(self):
|
|
373
|
# An update of the ignore file can potentially change the
|
|
373
|
# An update of the ignore file can potentially change the
|
|
374
|
# states of all unknown and ignored files.
|
|
374
|
# states of all unknown and ignored files.
|
|
375
|
|
|
375
|
|
|
376
|
# XXX If the user has other ignore files outside the repo, or
|
|
376
|
# XXX If the user has other ignore files outside the repo, or
|
|
377
|
# changes their list of ignore files at run time, we'll
|
|
377
|
# changes their list of ignore files at run time, we'll
|
|
378
|
# potentially never see changes to them. We could get the
|
|
378
|
# potentially never see changes to them. We could get the
|
|
379
|
# client to report to us what ignore data they're using.
|
|
379
|
# client to report to us what ignore data they're using.
|
|
380
|
# But it's easier to do nothing than to open that can of
|
|
380
|
# But it's easier to do nothing than to open that can of
|
|
381
|
# worms.
|
|
381
|
# worms.
|
|
382
|
|
|
382
|
|
|
383
|
if self.repo.dirstate.ignorefunc is not None:
|
|
383
|
if self.repo.dirstate.ignorefunc is not None:
|
|
384
|
self.repo.dirstate.ignorefunc = None
|
|
384
|
self.repo.dirstate.ignorefunc = None
|
|
385
|
self.ui.note('rescanning due to .hgignore change\n')
|
|
385
|
self.ui.note('rescanning due to .hgignore change\n')
|
|
386
|
self.scan()
|
|
386
|
self.scan()
|
|
387
|
|
|
387
|
|
|
388
|
def getstat(self, wpath):
|
|
388
|
def getstat(self, wpath):
|
|
389
|
try:
|
|
389
|
try:
|
|
390
|
return self.statcache[wpath]
|
|
390
|
return self.statcache[wpath]
|
|
391
|
except KeyError:
|
|
391
|
except KeyError:
|
|
392
|
try:
|
|
392
|
try:
|
|
393
|
return self.stat(wpath)
|
|
393
|
return self.stat(wpath)
|
|
394
|
except OSError, err:
|
|
394
|
except OSError, err:
|
|
395
|
if err.errno != errno.ENOENT:
|
|
395
|
if err.errno != errno.ENOENT:
|
|
396
|
raise
|
|
396
|
raise
|
|
397
|
|
|
397
|
|
|
398
|
def stat(self, wpath):
|
|
398
|
def stat(self, wpath):
|
|
399
|
try:
|
|
399
|
try:
|
|
400
|
st = os.lstat(join(self.wprefix, wpath))
|
|
400
|
st = os.lstat(join(self.wprefix, wpath))
|
|
401
|
ret = st.st_mode, st.st_size, st.st_mtime
|
|
401
|
ret = st.st_mode, st.st_size, st.st_mtime
|
|
402
|
self.statcache[wpath] = ret
|
|
402
|
self.statcache[wpath] = ret
|
|
403
|
return ret
|
|
403
|
return ret
|
|
404
|
except OSError, err:
|
|
404
|
except OSError, err:
|
|
405
|
self.statcache.pop(wpath, None)
|
|
405
|
self.statcache.pop(wpath, None)
|
|
406
|
raise
|
|
406
|
raise
|
|
407
|
|
|
407
|
|
|
408
|
def created(self, wpath):
|
|
408
|
def created(self, wpath):
|
|
409
|
if wpath == '.hgignore':
|
|
409
|
if wpath == '.hgignore':
|
|
410
|
self.update_hgignore()
|
|
410
|
self.update_hgignore()
|
|
411
|
try:
|
|
411
|
try:
|
|
412
|
st = self.stat(wpath)
|
|
412
|
st = self.stat(wpath)
|
|
413
|
if stat.S_ISREG(st[0]):
|
|
413
|
if stat.S_ISREG(st[0]):
|
|
414
|
self.updatestatus(wpath, st)
|
|
414
|
self.updatestatus(wpath, st)
|
|
415
|
except OSError, err:
|
|
415
|
except OSError, err:
|
|
416
|
pass
|
|
416
|
pass
|
|
417
|
|
|
417
|
|
|
418
|
def modified(self, wpath):
|
|
418
|
def modified(self, wpath):
|
|
419
|
if wpath == '.hgignore':
|
|
419
|
if wpath == '.hgignore':
|
|
420
|
self.update_hgignore()
|
|
420
|
self.update_hgignore()
|
|
421
|
try:
|
|
421
|
try:
|
|
422
|
st = self.stat(wpath)
|
|
422
|
st = self.stat(wpath)
|
|
423
|
if stat.S_ISREG(st[0]):
|
|
423
|
if stat.S_ISREG(st[0]):
|
|
424
|
if self.repo.dirstate[wpath] in 'lmn':
|
|
424
|
if self.repo.dirstate[wpath] in 'lmn':
|
|
425
|
self.updatestatus(wpath, st)
|
|
425
|
self.updatestatus(wpath, st)
|
|
426
|
except OSError:
|
|
426
|
except OSError:
|
|
427
|
pass
|
|
427
|
pass
|
|
428
|
|
|
428
|
|
|
429
|
def deleted(self, wpath):
|
|
429
|
def deleted(self, wpath):
|
|
430
|
if wpath == '.hgignore':
|
|
430
|
if wpath == '.hgignore':
|
|
431
|
self.update_hgignore()
|
|
431
|
self.update_hgignore()
|
|
432
|
elif wpath.startswith('.hg/'):
|
|
432
|
elif wpath.startswith('.hg/'):
|
|
433
|
if wpath == '.hg/wlock':
|
|
433
|
if wpath == '.hg/wlock':
|
|
434
|
self.check_dirstate()
|
|
434
|
self.check_dirstate()
|
|
435
|
return
|
|
435
|
return
|
|
436
|
|
|
436
|
|
|
437
|
self.updatestatus(wpath, None)
|
|
437
|
self.updatestatus(wpath, None)
|
|
438
|
|
|
438
|
|
|
439
|
def schedule_work(self, wpath, evt):
|
|
439
|
def schedule_work(self, wpath, evt):
|
|
440
|
self.eventq.setdefault(wpath, [])
|
|
440
|
self.eventq.setdefault(wpath, [])
|
|
441
|
prev = self.eventq[wpath]
|
|
441
|
prev = self.eventq[wpath]
|
|
442
|
try:
|
|
442
|
try:
|
|
443
|
if prev and evt == 'm' and prev[-1] in 'cm':
|
|
443
|
if prev and evt == 'm' and prev[-1] in 'cm':
|
|
444
|
return
|
|
444
|
return
|
|
445
|
self.eventq[wpath].append(evt)
|
|
445
|
self.eventq[wpath].append(evt)
|
|
446
|
finally:
|
|
446
|
finally:
|
|
447
|
self.deferred += 1
|
|
447
|
self.deferred += 1
|
|
448
|
self.timeout = 250
|
|
448
|
self.timeout = 250
|
|
449
|
|
|
449
|
|
|
450
|
def deferred_event(self, wpath, evt):
|
|
450
|
def deferred_event(self, wpath, evt):
|
|
451
|
if evt == 'c':
|
|
451
|
if evt == 'c':
|
|
452
|
self.created(wpath)
|
|
452
|
self.created(wpath)
|
|
453
|
elif evt == 'm':
|
|
453
|
elif evt == 'm':
|
|
454
|
self.modified(wpath)
|
|
454
|
self.modified(wpath)
|
|
455
|
elif evt == 'd':
|
|
455
|
elif evt == 'd':
|
|
456
|
self.deleted(wpath)
|
|
456
|
self.deleted(wpath)
|
|
457
|
|
|
457
|
|
|
458
|
def process_create(self, wpath, evt):
|
|
458
|
def process_create(self, wpath, evt):
|
|
459
|
if self.ui.debugflag:
|
|
459
|
if self.ui.debugflag:
|
|
460
|
self.ui.note(_('%s event: created %s\n') %
|
|
460
|
self.ui.note(_('%s event: created %s\n') %
|
|
461
|
(self.event_time(), wpath))
|
|
461
|
(self.event_time(), wpath))
|
|
462
|
|
|
462
|
|
|
463
|
if evt.mask & inotify.IN_ISDIR:
|
|
463
|
if evt.mask & inotify.IN_ISDIR:
|
|
464
|
self.scan(wpath)
|
|
464
|
self.scan(wpath)
|
|
465
|
else:
|
|
465
|
else:
|
|
466
|
self.schedule_work(wpath, 'c')
|
|
466
|
self.schedule_work(wpath, 'c')
|
|
467
|
|
|
467
|
|
|
468
|
def process_delete(self, wpath, evt):
|
|
468
|
def process_delete(self, wpath, evt):
|
|
469
|
if self.ui.debugflag:
|
|
469
|
if self.ui.debugflag:
|
|
470
|
self.ui.note(('%s event: deleted %s\n') %
|
|
470
|
self.ui.note(('%s event: deleted %s\n') %
|
|
471
|
(self.event_time(), wpath))
|
|
471
|
(self.event_time(), wpath))
|
|
472
|
|
|
472
|
|
|
473
|
if evt.mask & inotify.IN_ISDIR:
|
|
473
|
if evt.mask & inotify.IN_ISDIR:
|
|
474
|
self.scan(wpath)
|
|
474
|
self.scan(wpath)
|
|
475
|
else:
|
|
475
|
else:
|
|
476
|
self.schedule_work(wpath, 'd')
|
|
476
|
self.schedule_work(wpath, 'd')
|
|
477
|
|
|
477
|
|
|
478
|
def process_modify(self, wpath, evt):
|
|
478
|
def process_modify(self, wpath, evt):
|
|
479
|
if self.ui.debugflag:
|
|
479
|
if self.ui.debugflag:
|
|
480
|
self.ui.note(_('%s event: modified %s\n') %
|
|
480
|
self.ui.note(_('%s event: modified %s\n') %
|
|
481
|
(self.event_time(), wpath))
|
|
481
|
(self.event_time(), wpath))
|
|
482
|
|
|
482
|
|
|
483
|
if not (evt.mask & inotify.IN_ISDIR):
|
|
483
|
if not (evt.mask & inotify.IN_ISDIR):
|
|
484
|
self.schedule_work(wpath, 'm')
|
|
484
|
self.schedule_work(wpath, 'm')
|
|
485
|
|
|
485
|
|
|
486
|
def process_unmount(self, evt):
|
|
486
|
def process_unmount(self, evt):
|
|
487
|
self.ui.warn(_('filesystem containing %s was unmounted\n') %
|
|
487
|
self.ui.warn(_('filesystem containing %s was unmounted\n') %
|
|
488
|
evt.fullpath)
|
|
488
|
evt.fullpath)
|
|
489
|
sys.exit(0)
|
|
489
|
sys.exit(0)
|
|
490
|
|
|
490
|
|
|
491
|
def handle_event(self, fd, event):
|
|
491
|
def handle_event(self, fd, event):
|
|
492
|
if self.ui.debugflag:
|
|
492
|
if self.ui.debugflag:
|
|
493
|
self.ui.note('%s readable: %d bytes\n' %
|
|
493
|
self.ui.note('%s readable: %d bytes\n' %
|
|
494
|
(self.event_time(), self.threshold.readable()))
|
|
494
|
(self.event_time(), self.threshold.readable()))
|
|
495
|
if not self.threshold():
|
|
495
|
if not self.threshold():
|
|
496
|
if self.registered:
|
|
496
|
if self.registered:
|
|
497
|
if self.ui.debugflag:
|
|
497
|
if self.ui.debugflag:
|
|
498
|
self.ui.note('%s below threshold - unhooking\n' %
|
|
498
|
self.ui.note('%s below threshold - unhooking\n' %
|
|
499
|
(self.event_time()))
|
|
499
|
(self.event_time()))
|
|
500
|
self.master.poll.unregister(fd)
|
|
500
|
self.master.poll.unregister(fd)
|
|
501
|
self.registered = False
|
|
501
|
self.registered = False
|
|
502
|
self.timeout = 250
|
|
502
|
self.timeout = 250
|
|
503
|
else:
|
|
503
|
else:
|
|
504
|
self.read_events()
|
|
504
|
self.read_events()
|
|
505
|
|
|
505
|
|
|
506
|
def read_events(self, bufsize=None):
|
|
506
|
def read_events(self, bufsize=None):
|
|
507
|
events = self.watcher.read(bufsize)
|
|
507
|
events = self.watcher.read(bufsize)
|
|
508
|
if self.ui.debugflag:
|
|
508
|
if self.ui.debugflag:
|
|
509
|
self.ui.note('%s reading %d events\n' %
|
|
509
|
self.ui.note('%s reading %d events\n' %
|
|
510
|
(self.event_time(), len(events)))
|
|
510
|
(self.event_time(), len(events)))
|
|
511
|
for evt in events:
|
|
511
|
for evt in events:
|
|
512
|
wpath = self.wpath(evt)
|
|
512
|
wpath = self.wpath(evt)
|
|
513
|
if evt.mask & inotify.IN_UNMOUNT:
|
|
513
|
if evt.mask & inotify.IN_UNMOUNT:
|
|
514
|
self.process_unmount(wpath, evt)
|
|
514
|
self.process_unmount(wpath, evt)
|
|
515
|
elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
|
|
515
|
elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
|
|
516
|
self.process_modify(wpath, evt)
|
|
516
|
self.process_modify(wpath, evt)
|
|
517
|
elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
|
|
517
|
elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
|
|
518
|
inotify.IN_MOVED_FROM):
|
|
518
|
inotify.IN_MOVED_FROM):
|
|
519
|
self.process_delete(wpath, evt)
|
|
519
|
self.process_delete(wpath, evt)
|
|
520
|
elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
|
|
520
|
elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
|
|
521
|
self.process_create(wpath, evt)
|
|
521
|
self.process_create(wpath, evt)
|
|
522
|
|
|
522
|
|
|
523
|
def handle_timeout(self):
|
|
523
|
def handle_timeout(self):
|
|
524
|
if not self.registered:
|
|
524
|
if not self.registered:
|
|
525
|
if self.ui.debugflag:
|
|
525
|
if self.ui.debugflag:
|
|
526
|
self.ui.note('%s hooking back up with %d bytes readable\n' %
|
|
526
|
self.ui.note('%s hooking back up with %d bytes readable\n' %
|
|
527
|
(self.event_time(), self.threshold.readable()))
|
|
527
|
(self.event_time(), self.threshold.readable()))
|
|
528
|
self.read_events(0)
|
|
528
|
self.read_events(0)
|
|
529
|
self.master.poll.register(self, select.POLLIN)
|
|
529
|
self.master.poll.register(self, select.POLLIN)
|
|
530
|
self.registered = True
|
|
530
|
self.registered = True
|
|
531
|
|
|
531
|
|
|
532
|
if self.eventq:
|
|
532
|
if self.eventq:
|
|
533
|
if self.ui.debugflag:
|
|
533
|
if self.ui.debugflag:
|
|
534
|
self.ui.note('%s processing %d deferred events as %d\n' %
|
|
534
|
self.ui.note('%s processing %d deferred events as %d\n' %
|
|
535
|
(self.event_time(), self.deferred,
|
|
535
|
(self.event_time(), self.deferred,
|
|
536
|
len(self.eventq)))
|
|
536
|
len(self.eventq)))
|
|
537
|
eventq = self.eventq.items()
|
|
537
|
eventq = self.eventq.items()
|
|
538
|
eventq.sort()
|
|
538
|
eventq.sort()
|
|
539
|
for wpath, evts in eventq:
|
|
539
|
for wpath, evts in eventq:
|
|
540
|
for evt in evts:
|
|
540
|
for evt in evts:
|
|
541
|
self.deferred_event(wpath, evt)
|
|
541
|
self.deferred_event(wpath, evt)
|
|
542
|
self.eventq.clear()
|
|
542
|
self.eventq.clear()
|
|
543
|
self.deferred = 0
|
|
543
|
self.deferred = 0
|
|
544
|
self.timeout = None
|
|
544
|
self.timeout = None
|
|
545
|
|
|
545
|
|
|
546
|
def shutdown(self):
|
|
546
|
def shutdown(self):
|
|
547
|
self.watcher.close()
|
|
547
|
self.watcher.close()
|
|
548
|
|
|
548
|
|
|
549
|
class Server(object):
|
|
549
|
class Server(object):
|
|
550
|
poll_events = select.POLLIN
|
|
550
|
poll_events = select.POLLIN
|
|
551
|
|
|
551
|
|
|
552
|
def __init__(self, ui, repo, watcher, timeout):
|
|
552
|
def __init__(self, ui, repo, watcher, timeout):
|
|
553
|
self.ui = ui
|
|
553
|
self.ui = ui
|
|
554
|
self.repo = repo
|
|
554
|
self.repo = repo
|
|
555
|
self.watcher = watcher
|
|
555
|
self.watcher = watcher
|
|
556
|
self.timeout = timeout
|
|
556
|
self.timeout = timeout
|
|
557
|
self.sock = socket.socket(socket.AF_UNIX)
|
|
557
|
self.sock = socket.socket(socket.AF_UNIX)
|
|
558
|
self.sockpath = self.repo.join('inotify.sock')
|
|
558
|
self.sockpath = self.repo.join('inotify.sock')
|
|
|
|
|
559
|
self.realsockpath = None
|
|
559
|
try:
|
|
560
|
try:
|
|
560
|
self.sock.bind(self.sockpath)
|
|
561
|
self.sock.bind(self.sockpath)
|
|
561
|
except socket.error, err:
|
|
562
|
except socket.error, err:
|
|
562
|
if err[0] == errno.EADDRINUSE:
|
|
563
|
if err[0] == errno.EADDRINUSE:
|
|
563
|
raise AlreadyStartedException(_('could not start server: %s') \
|
|
564
|
raise AlreadyStartedException(_('could not start server: %s')
|
|
564
|
% err[1])
|
|
565
|
% err[1])
|
|
|
|
|
566
|
if err[0] == "AF_UNIX path too long":
|
|
|
|
|
567
|
tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
|
|
|
|
|
568
|
self.realsockpath = os.path.join(tempdir, "inotify.sock")
|
|
|
|
|
569
|
try:
|
|
|
|
|
570
|
self.sock.bind(self.realsockpath)
|
|
|
|
|
571
|
os.symlink(self.realsockpath, self.sockpath)
|
|
|
|
|
572
|
except (OSError, socket.error), inst:
|
|
|
|
|
573
|
try:
|
|
|
|
|
574
|
os.unlink(self.realsockpath)
|
|
|
|
|
575
|
except:
|
|
|
|
|
576
|
pass
|
|
|
|
|
577
|
os.rmdir(tempdir)
|
|
|
|
|
578
|
if inst.errno == errno.EEXIST:
|
|
|
|
|
579
|
raise AlreadyStartedException(_('could not start server: %s')
|
|
|
|
|
580
|
% inst.strerror)
|
|
|
|
|
581
|
raise
|
|
|
|
|
582
|
else:
|
|
565
|
raise
|
|
583
|
raise
|
|
566
|
self.sock.listen(5)
|
|
584
|
self.sock.listen(5)
|
|
567
|
self.fileno = self.sock.fileno
|
|
585
|
self.fileno = self.sock.fileno
|
|
568
|
|
|
586
|
|
|
569
|
def handle_timeout(self):
|
|
587
|
def handle_timeout(self):
|
|
570
|
pass
|
|
588
|
pass
|
|
571
|
|
|
589
|
|
|
572
|
def handle_event(self, fd, event):
|
|
590
|
def handle_event(self, fd, event):
|
|
573
|
sock, addr = self.sock.accept()
|
|
591
|
sock, addr = self.sock.accept()
|
|
574
|
|
|
592
|
|
|
575
|
cs = common.recvcs(sock)
|
|
593
|
cs = common.recvcs(sock)
|
|
576
|
version = ord(cs.read(1))
|
|
594
|
version = ord(cs.read(1))
|
|
577
|
|
|
595
|
|
|
578
|
sock.sendall(chr(common.version))
|
|
596
|
sock.sendall(chr(common.version))
|
|
579
|
|
|
597
|
|
|
580
|
if version != common.version:
|
|
598
|
if version != common.version:
|
|
581
|
self.ui.warn(_('received query from incompatible client '
|
|
599
|
self.ui.warn(_('received query from incompatible client '
|
|
582
|
'version %d\n') % version)
|
|
600
|
'version %d\n') % version)
|
|
583
|
return
|
|
601
|
return
|
|
584
|
|
|
602
|
|
|
585
|
names = cs.read().split('\0')
|
|
603
|
names = cs.read().split('\0')
|
|
586
|
|
|
604
|
|
|
587
|
states = names.pop()
|
|
605
|
states = names.pop()
|
|
588
|
|
|
606
|
|
|
589
|
self.ui.note(_('answering query for %r\n') % states)
|
|
607
|
self.ui.note(_('answering query for %r\n') % states)
|
|
590
|
|
|
608
|
|
|
591
|
if self.watcher.timeout:
|
|
609
|
if self.watcher.timeout:
|
|
592
|
# We got a query while a rescan is pending. Make sure we
|
|
610
|
# We got a query while a rescan is pending. Make sure we
|
|
593
|
# rescan before responding, or we could give back a wrong
|
|
611
|
# rescan before responding, or we could give back a wrong
|
|
594
|
# answer.
|
|
612
|
# answer.
|
|
595
|
self.watcher.handle_timeout()
|
|
613
|
self.watcher.handle_timeout()
|
|
596
|
|
|
614
|
|
|
597
|
if not names:
|
|
615
|
if not names:
|
|
598
|
def genresult(states, tree):
|
|
616
|
def genresult(states, tree):
|
|
599
|
for fn, state in self.watcher.walk(states, tree):
|
|
617
|
for fn, state in self.watcher.walk(states, tree):
|
|
600
|
yield fn
|
|
618
|
yield fn
|
|
601
|
else:
|
|
619
|
else:
|
|
602
|
def genresult(states, tree):
|
|
620
|
def genresult(states, tree):
|
|
603
|
for fn in names:
|
|
621
|
for fn in names:
|
|
604
|
l = self.watcher.lookup(fn, tree)
|
|
622
|
l = self.watcher.lookup(fn, tree)
|
|
605
|
try:
|
|
623
|
try:
|
|
606
|
if l in states:
|
|
624
|
if l in states:
|
|
607
|
yield fn
|
|
625
|
yield fn
|
|
608
|
except TypeError:
|
|
626
|
except TypeError:
|
|
609
|
for f, s in self.watcher.walk(states, l, fn):
|
|
627
|
for f, s in self.watcher.walk(states, l, fn):
|
|
610
|
yield f
|
|
628
|
yield f
|
|
611
|
|
|
629
|
|
|
612
|
results = ['\0'.join(r) for r in [
|
|
630
|
results = ['\0'.join(r) for r in [
|
|
613
|
genresult('l', self.watcher.statustrees['l']),
|
|
631
|
genresult('l', self.watcher.statustrees['l']),
|
|
614
|
genresult('m', self.watcher.statustrees['m']),
|
|
632
|
genresult('m', self.watcher.statustrees['m']),
|
|
615
|
genresult('a', self.watcher.statustrees['a']),
|
|
633
|
genresult('a', self.watcher.statustrees['a']),
|
|
616
|
genresult('r', self.watcher.statustrees['r']),
|
|
634
|
genresult('r', self.watcher.statustrees['r']),
|
|
617
|
genresult('!', self.watcher.statustrees['!']),
|
|
635
|
genresult('!', self.watcher.statustrees['!']),
|
|
618
|
'?' in states and genresult('?', self.watcher.statustrees['?']) or [],
|
|
636
|
'?' in states and genresult('?', self.watcher.statustrees['?']) or [],
|
|
619
|
[],
|
|
637
|
[],
|
|
620
|
'c' in states and genresult('n', self.watcher.tree) or [],
|
|
638
|
'c' in states and genresult('n', self.watcher.tree) or [],
|
|
621
|
]]
|
|
639
|
]]
|
|
622
|
|
|
640
|
|
|
623
|
try:
|
|
641
|
try:
|
|
624
|
try:
|
|
642
|
try:
|
|
625
|
sock.sendall(struct.pack(common.resphdrfmt,
|
|
643
|
sock.sendall(struct.pack(common.resphdrfmt,
|
|
626
|
*map(len, results)))
|
|
644
|
*map(len, results)))
|
|
627
|
sock.sendall(''.join(results))
|
|
645
|
sock.sendall(''.join(results))
|
|
628
|
finally:
|
|
646
|
finally:
|
|
629
|
sock.shutdown(socket.SHUT_WR)
|
|
647
|
sock.shutdown(socket.SHUT_WR)
|
|
630
|
except socket.error, err:
|
|
648
|
except socket.error, err:
|
|
631
|
if err[0] != errno.EPIPE:
|
|
649
|
if err[0] != errno.EPIPE:
|
|
632
|
raise
|
|
650
|
raise
|
|
633
|
|
|
651
|
|
|
634
|
def shutdown(self):
|
|
652
|
def shutdown(self):
|
|
635
|
self.sock.close()
|
|
653
|
self.sock.close()
|
|
636
|
try:
|
|
654
|
try:
|
|
637
|
os.unlink(self.sockpath)
|
|
655
|
os.unlink(self.sockpath)
|
|
|
|
|
656
|
if self.realsockpath:
|
|
|
|
|
657
|
os.unlink(self.realsockpath)
|
|
|
|
|
658
|
os.rmdir(os.path.dirname(self.realsockpath))
|
|
638
|
except OSError, err:
|
|
659
|
except OSError, err:
|
|
639
|
if err.errno != errno.ENOENT:
|
|
660
|
if err.errno != errno.ENOENT:
|
|
640
|
raise
|
|
661
|
raise
|
|
641
|
|
|
662
|
|
|
642
|
class Master(object):
|
|
663
|
class Master(object):
|
|
643
|
def __init__(self, ui, repo, timeout=None):
|
|
664
|
def __init__(self, ui, repo, timeout=None):
|
|
644
|
self.ui = ui
|
|
665
|
self.ui = ui
|
|
645
|
self.repo = repo
|
|
666
|
self.repo = repo
|
|
646
|
self.poll = select.poll()
|
|
667
|
self.poll = select.poll()
|
|
647
|
self.watcher = Watcher(ui, repo, self)
|
|
668
|
self.watcher = Watcher(ui, repo, self)
|
|
648
|
self.server = Server(ui, repo, self.watcher, timeout)
|
|
669
|
self.server = Server(ui, repo, self.watcher, timeout)
|
|
649
|
self.table = {}
|
|
670
|
self.table = {}
|
|
650
|
for obj in (self.watcher, self.server):
|
|
671
|
for obj in (self.watcher, self.server):
|
|
651
|
fd = obj.fileno()
|
|
672
|
fd = obj.fileno()
|
|
652
|
self.table[fd] = obj
|
|
673
|
self.table[fd] = obj
|
|
653
|
self.poll.register(fd, obj.poll_events)
|
|
674
|
self.poll.register(fd, obj.poll_events)
|
|
654
|
|
|
675
|
|
|
655
|
def register(self, fd, mask):
|
|
676
|
def register(self, fd, mask):
|
|
656
|
self.poll.register(fd, mask)
|
|
677
|
self.poll.register(fd, mask)
|
|
657
|
|
|
678
|
|
|
658
|
def shutdown(self):
|
|
679
|
def shutdown(self):
|
|
659
|
for obj in self.table.itervalues():
|
|
680
|
for obj in self.table.itervalues():
|
|
660
|
obj.shutdown()
|
|
681
|
obj.shutdown()
|
|
661
|
|
|
682
|
|
|
662
|
def run(self):
|
|
683
|
def run(self):
|
|
663
|
self.watcher.setup()
|
|
684
|
self.watcher.setup()
|
|
664
|
self.ui.note(_('finished setup\n'))
|
|
685
|
self.ui.note(_('finished setup\n'))
|
|
665
|
if os.getenv('TIME_STARTUP'):
|
|
686
|
if os.getenv('TIME_STARTUP'):
|
|
666
|
sys.exit(0)
|
|
687
|
sys.exit(0)
|
|
667
|
while True:
|
|
688
|
while True:
|
|
668
|
timeout = None
|
|
689
|
timeout = None
|
|
669
|
timeobj = None
|
|
690
|
timeobj = None
|
|
670
|
for obj in self.table.itervalues():
|
|
691
|
for obj in self.table.itervalues():
|
|
671
|
if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
|
|
692
|
if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
|
|
672
|
timeout, timeobj = obj.timeout, obj
|
|
693
|
timeout, timeobj = obj.timeout, obj
|
|
673
|
try:
|
|
694
|
try:
|
|
674
|
if self.ui.debugflag:
|
|
695
|
if self.ui.debugflag:
|
|
675
|
if timeout is None:
|
|
696
|
if timeout is None:
|
|
676
|
self.ui.note('polling: no timeout\n')
|
|
697
|
self.ui.note('polling: no timeout\n')
|
|
677
|
else:
|
|
698
|
else:
|
|
678
|
self.ui.note('polling: %sms timeout\n' % timeout)
|
|
699
|
self.ui.note('polling: %sms timeout\n' % timeout)
|
|
679
|
events = self.poll.poll(timeout)
|
|
700
|
events = self.poll.poll(timeout)
|
|
680
|
except select.error, err:
|
|
701
|
except select.error, err:
|
|
681
|
if err[0] == errno.EINTR:
|
|
702
|
if err[0] == errno.EINTR:
|
|
682
|
continue
|
|
703
|
continue
|
|
683
|
raise
|
|
704
|
raise
|
|
684
|
if events:
|
|
705
|
if events:
|
|
685
|
for fd, event in events:
|
|
706
|
for fd, event in events:
|
|
686
|
self.table[fd].handle_event(fd, event)
|
|
707
|
self.table[fd].handle_event(fd, event)
|
|
687
|
elif timeobj:
|
|
708
|
elif timeobj:
|
|
688
|
timeobj.handle_timeout()
|
|
709
|
timeobj.handle_timeout()
|
|
689
|
|
|
710
|
|
|
690
|
def start(ui, repo):
|
|
711
|
def start(ui, repo):
|
|
691
|
m = Master(ui, repo)
|
|
712
|
m = Master(ui, repo)
|
|
692
|
sys.stdout.flush()
|
|
713
|
sys.stdout.flush()
|
|
693
|
sys.stderr.flush()
|
|
714
|
sys.stderr.flush()
|
|
694
|
|
|
715
|
|
|
695
|
pid = os.fork()
|
|
716
|
pid = os.fork()
|
|
696
|
if pid:
|
|
717
|
if pid:
|
|
697
|
return pid
|
|
718
|
return pid
|
|
698
|
|
|
719
|
|
|
699
|
os.setsid()
|
|
720
|
os.setsid()
|
|
700
|
|
|
721
|
|
|
701
|
fd = os.open('/dev/null', os.O_RDONLY)
|
|
722
|
fd = os.open('/dev/null', os.O_RDONLY)
|
|
702
|
os.dup2(fd, 0)
|
|
723
|
os.dup2(fd, 0)
|
|
703
|
if fd > 0:
|
|
724
|
if fd > 0:
|
|
704
|
os.close(fd)
|
|
725
|
os.close(fd)
|
|
705
|
|
|
726
|
|
|
706
|
fd = os.open(ui.config('inotify', 'log', '/dev/null'),
|
|
727
|
fd = os.open(ui.config('inotify', 'log', '/dev/null'),
|
|
707
|
os.O_RDWR | os.O_CREAT | os.O_TRUNC)
|
|
728
|
os.O_RDWR | os.O_CREAT | os.O_TRUNC)
|
|
708
|
os.dup2(fd, 1)
|
|
729
|
os.dup2(fd, 1)
|
|
709
|
os.dup2(fd, 2)
|
|
730
|
os.dup2(fd, 2)
|
|
710
|
if fd > 2:
|
|
731
|
if fd > 2:
|
|
711
|
os.close(fd)
|
|
732
|
os.close(fd)
|
|
712
|
|
|
733
|
|
|
713
|
try:
|
|
734
|
try:
|
|
714
|
m.run()
|
|
735
|
m.run()
|
|
715
|
finally:
|
|
736
|
finally:
|
|
716
|
m.shutdown()
|
|
737
|
m.shutdown()
|
|
717
|
os._exit(0)
|
|
738
|
os._exit(0)
|