##// END OF EJS Templates
hgext: fixup a couple missed file().read() instances
Matt Mackall -
r14179:64481eee default
parent child Browse files
Show More
@@ -1,358 +1,360
1 # monotone.py - monotone support for the convert extension
1 # monotone.py - monotone support for the convert extension
2 #
2 #
3 # Copyright 2008, 2009 Mikkel Fahnoe Jorgensen <mikkel@dvide.com> and
3 # Copyright 2008, 2009 Mikkel Fahnoe Jorgensen <mikkel@dvide.com> and
4 # others
4 # others
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os, re
9 import os, re
10 from mercurial import util
10 from mercurial import util
11 from common import NoRepo, commit, converter_source, checktool
11 from common import NoRepo, commit, converter_source, checktool
12 from common import commandline
12 from common import commandline
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14
14
15 class monotone_source(converter_source, commandline):
15 class monotone_source(converter_source, commandline):
16 def __init__(self, ui, path=None, rev=None):
16 def __init__(self, ui, path=None, rev=None):
17 converter_source.__init__(self, ui, path, rev)
17 converter_source.__init__(self, ui, path, rev)
18 commandline.__init__(self, ui, 'mtn')
18 commandline.__init__(self, ui, 'mtn')
19
19
20 self.ui = ui
20 self.ui = ui
21 self.path = path
21 self.path = path
22 self.automatestdio = False
22 self.automatestdio = False
23 self.rev = rev
23 self.rev = rev
24
24
25 norepo = NoRepo(_("%s does not look like a monotone repository")
25 norepo = NoRepo(_("%s does not look like a monotone repository")
26 % path)
26 % path)
27 if not os.path.exists(os.path.join(path, '_MTN')):
27 if not os.path.exists(os.path.join(path, '_MTN')):
28 # Could be a monotone repository (SQLite db file)
28 # Could be a monotone repository (SQLite db file)
29 try:
29 try:
30 header = file(path, 'rb').read(16)
30 f = file(path, 'rb')
31 header = f.read(16)
32 f.close()
31 except:
33 except:
32 header = ''
34 header = ''
33 if header != 'SQLite format 3\x00':
35 if header != 'SQLite format 3\x00':
34 raise norepo
36 raise norepo
35
37
36 # regular expressions for parsing monotone output
38 # regular expressions for parsing monotone output
37 space = r'\s*'
39 space = r'\s*'
38 name = r'\s+"((?:\\"|[^"])*)"\s*'
40 name = r'\s+"((?:\\"|[^"])*)"\s*'
39 value = name
41 value = name
40 revision = r'\s+\[(\w+)\]\s*'
42 revision = r'\s+\[(\w+)\]\s*'
41 lines = r'(?:.|\n)+'
43 lines = r'(?:.|\n)+'
42
44
43 self.dir_re = re.compile(space + "dir" + name)
45 self.dir_re = re.compile(space + "dir" + name)
44 self.file_re = re.compile(space + "file" + name +
46 self.file_re = re.compile(space + "file" + name +
45 "content" + revision)
47 "content" + revision)
46 self.add_file_re = re.compile(space + "add_file" + name +
48 self.add_file_re = re.compile(space + "add_file" + name +
47 "content" + revision)
49 "content" + revision)
48 self.patch_re = re.compile(space + "patch" + name +
50 self.patch_re = re.compile(space + "patch" + name +
49 "from" + revision + "to" + revision)
51 "from" + revision + "to" + revision)
50 self.rename_re = re.compile(space + "rename" + name + "to" + name)
52 self.rename_re = re.compile(space + "rename" + name + "to" + name)
51 self.delete_re = re.compile(space + "delete" + name)
53 self.delete_re = re.compile(space + "delete" + name)
52 self.tag_re = re.compile(space + "tag" + name + "revision" +
54 self.tag_re = re.compile(space + "tag" + name + "revision" +
53 revision)
55 revision)
54 self.cert_re = re.compile(lines + space + "name" + name +
56 self.cert_re = re.compile(lines + space + "name" + name +
55 "value" + value)
57 "value" + value)
56
58
57 attr = space + "file" + lines + space + "attr" + space
59 attr = space + "file" + lines + space + "attr" + space
58 self.attr_execute_re = re.compile(attr + '"mtn:execute"' +
60 self.attr_execute_re = re.compile(attr + '"mtn:execute"' +
59 space + '"true"')
61 space + '"true"')
60
62
61 # cached data
63 # cached data
62 self.manifest_rev = None
64 self.manifest_rev = None
63 self.manifest = None
65 self.manifest = None
64 self.files = None
66 self.files = None
65 self.dirs = None
67 self.dirs = None
66
68
67 checktool('mtn', abort=False)
69 checktool('mtn', abort=False)
68
70
69 def mtnrun(self, *args, **kwargs):
71 def mtnrun(self, *args, **kwargs):
70 if self.automatestdio:
72 if self.automatestdio:
71 return self.mtnrunstdio(*args, **kwargs)
73 return self.mtnrunstdio(*args, **kwargs)
72 else:
74 else:
73 return self.mtnrunsingle(*args, **kwargs)
75 return self.mtnrunsingle(*args, **kwargs)
74
76
75 def mtnrunsingle(self, *args, **kwargs):
77 def mtnrunsingle(self, *args, **kwargs):
76 kwargs['d'] = self.path
78 kwargs['d'] = self.path
77 return self.run0('automate', *args, **kwargs)
79 return self.run0('automate', *args, **kwargs)
78
80
79 def mtnrunstdio(self, *args, **kwargs):
81 def mtnrunstdio(self, *args, **kwargs):
80 # Prepare the command in automate stdio format
82 # Prepare the command in automate stdio format
81 command = []
83 command = []
82 for k, v in kwargs.iteritems():
84 for k, v in kwargs.iteritems():
83 command.append("%s:%s" % (len(k), k))
85 command.append("%s:%s" % (len(k), k))
84 if v:
86 if v:
85 command.append("%s:%s" % (len(v), v))
87 command.append("%s:%s" % (len(v), v))
86 if command:
88 if command:
87 command.insert(0, 'o')
89 command.insert(0, 'o')
88 command.append('e')
90 command.append('e')
89
91
90 command.append('l')
92 command.append('l')
91 for arg in args:
93 for arg in args:
92 command += "%s:%s" % (len(arg), arg)
94 command += "%s:%s" % (len(arg), arg)
93 command.append('e')
95 command.append('e')
94 command = ''.join(command)
96 command = ''.join(command)
95
97
96 self.ui.debug("mtn: sending '%s'\n" % command)
98 self.ui.debug("mtn: sending '%s'\n" % command)
97 self.mtnwritefp.write(command)
99 self.mtnwritefp.write(command)
98 self.mtnwritefp.flush()
100 self.mtnwritefp.flush()
99
101
100 return self.mtnstdioreadcommandoutput(command)
102 return self.mtnstdioreadcommandoutput(command)
101
103
102 def mtnstdioreadpacket(self):
104 def mtnstdioreadpacket(self):
103 read = None
105 read = None
104 commandnbr = ''
106 commandnbr = ''
105 while read != ':':
107 while read != ':':
106 read = self.mtnreadfp.read(1)
108 read = self.mtnreadfp.read(1)
107 if not read:
109 if not read:
108 raise util.Abort(_('bad mtn packet - no end of commandnbr'))
110 raise util.Abort(_('bad mtn packet - no end of commandnbr'))
109 commandnbr += read
111 commandnbr += read
110 commandnbr = commandnbr[:-1]
112 commandnbr = commandnbr[:-1]
111
113
112 stream = self.mtnreadfp.read(1)
114 stream = self.mtnreadfp.read(1)
113 if stream not in 'mewptl':
115 if stream not in 'mewptl':
114 raise util.Abort(_('bad mtn packet - bad stream type %s' % stream))
116 raise util.Abort(_('bad mtn packet - bad stream type %s' % stream))
115
117
116 read = self.mtnreadfp.read(1)
118 read = self.mtnreadfp.read(1)
117 if read != ':':
119 if read != ':':
118 raise util.Abort(_('bad mtn packet - no divider before size'))
120 raise util.Abort(_('bad mtn packet - no divider before size'))
119
121
120 read = None
122 read = None
121 lengthstr = ''
123 lengthstr = ''
122 while read != ':':
124 while read != ':':
123 read = self.mtnreadfp.read(1)
125 read = self.mtnreadfp.read(1)
124 if not read:
126 if not read:
125 raise util.Abort(_('bad mtn packet - no end of packet size'))
127 raise util.Abort(_('bad mtn packet - no end of packet size'))
126 lengthstr += read
128 lengthstr += read
127 try:
129 try:
128 length = long(lengthstr[:-1])
130 length = long(lengthstr[:-1])
129 except TypeError:
131 except TypeError:
130 raise util.Abort(_('bad mtn packet - bad packet size %s')
132 raise util.Abort(_('bad mtn packet - bad packet size %s')
131 % lengthstr)
133 % lengthstr)
132
134
133 read = self.mtnreadfp.read(length)
135 read = self.mtnreadfp.read(length)
134 if len(read) != length:
136 if len(read) != length:
135 raise util.Abort(_("bad mtn packet - unable to read full packet "
137 raise util.Abort(_("bad mtn packet - unable to read full packet "
136 "read %s of %s") % (len(read), length))
138 "read %s of %s") % (len(read), length))
137
139
138 return (commandnbr, stream, length, read)
140 return (commandnbr, stream, length, read)
139
141
140 def mtnstdioreadcommandoutput(self, command):
142 def mtnstdioreadcommandoutput(self, command):
141 retval = []
143 retval = []
142 while True:
144 while True:
143 commandnbr, stream, length, output = self.mtnstdioreadpacket()
145 commandnbr, stream, length, output = self.mtnstdioreadpacket()
144 self.ui.debug('mtn: read packet %s:%s:%s\n' %
146 self.ui.debug('mtn: read packet %s:%s:%s\n' %
145 (commandnbr, stream, length))
147 (commandnbr, stream, length))
146
148
147 if stream == 'l':
149 if stream == 'l':
148 # End of command
150 # End of command
149 if output != '0':
151 if output != '0':
150 raise util.Abort(_("mtn command '%s' returned %s") %
152 raise util.Abort(_("mtn command '%s' returned %s") %
151 (command, output))
153 (command, output))
152 break
154 break
153 elif stream in 'ew':
155 elif stream in 'ew':
154 # Error, warning output
156 # Error, warning output
155 self.ui.warn(_('%s error:\n') % self.command)
157 self.ui.warn(_('%s error:\n') % self.command)
156 self.ui.warn(output)
158 self.ui.warn(output)
157 elif stream == 'p':
159 elif stream == 'p':
158 # Progress messages
160 # Progress messages
159 self.ui.debug('mtn: ' + output)
161 self.ui.debug('mtn: ' + output)
160 elif stream == 'm':
162 elif stream == 'm':
161 # Main stream - command output
163 # Main stream - command output
162 retval.append(output)
164 retval.append(output)
163
165
164 return ''.join(retval)
166 return ''.join(retval)
165
167
166 def mtnloadmanifest(self, rev):
168 def mtnloadmanifest(self, rev):
167 if self.manifest_rev == rev:
169 if self.manifest_rev == rev:
168 return
170 return
169 self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
171 self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
170 self.manifest_rev = rev
172 self.manifest_rev = rev
171 self.files = {}
173 self.files = {}
172 self.dirs = {}
174 self.dirs = {}
173
175
174 for e in self.manifest:
176 for e in self.manifest:
175 m = self.file_re.match(e)
177 m = self.file_re.match(e)
176 if m:
178 if m:
177 attr = ""
179 attr = ""
178 name = m.group(1)
180 name = m.group(1)
179 node = m.group(2)
181 node = m.group(2)
180 if self.attr_execute_re.match(e):
182 if self.attr_execute_re.match(e):
181 attr += "x"
183 attr += "x"
182 self.files[name] = (node, attr)
184 self.files[name] = (node, attr)
183 m = self.dir_re.match(e)
185 m = self.dir_re.match(e)
184 if m:
186 if m:
185 self.dirs[m.group(1)] = True
187 self.dirs[m.group(1)] = True
186
188
187 def mtnisfile(self, name, rev):
189 def mtnisfile(self, name, rev):
188 # a non-file could be a directory or a deleted or renamed file
190 # a non-file could be a directory or a deleted or renamed file
189 self.mtnloadmanifest(rev)
191 self.mtnloadmanifest(rev)
190 return name in self.files
192 return name in self.files
191
193
192 def mtnisdir(self, name, rev):
194 def mtnisdir(self, name, rev):
193 self.mtnloadmanifest(rev)
195 self.mtnloadmanifest(rev)
194 return name in self.dirs
196 return name in self.dirs
195
197
196 def mtngetcerts(self, rev):
198 def mtngetcerts(self, rev):
197 certs = {"author":"<missing>", "date":"<missing>",
199 certs = {"author":"<missing>", "date":"<missing>",
198 "changelog":"<missing>", "branch":"<missing>"}
200 "changelog":"<missing>", "branch":"<missing>"}
199 certlist = self.mtnrun("certs", rev)
201 certlist = self.mtnrun("certs", rev)
200 # mtn < 0.45:
202 # mtn < 0.45:
201 # key "test@selenic.com"
203 # key "test@selenic.com"
202 # mtn >= 0.45:
204 # mtn >= 0.45:
203 # key [ff58a7ffb771907c4ff68995eada1c4da068d328]
205 # key [ff58a7ffb771907c4ff68995eada1c4da068d328]
204 certlist = re.split('\n\n key ["\[]', certlist)
206 certlist = re.split('\n\n key ["\[]', certlist)
205 for e in certlist:
207 for e in certlist:
206 m = self.cert_re.match(e)
208 m = self.cert_re.match(e)
207 if m:
209 if m:
208 name, value = m.groups()
210 name, value = m.groups()
209 value = value.replace(r'\"', '"')
211 value = value.replace(r'\"', '"')
210 value = value.replace(r'\\', '\\')
212 value = value.replace(r'\\', '\\')
211 certs[name] = value
213 certs[name] = value
212 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
214 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
213 # and all times are stored in UTC
215 # and all times are stored in UTC
214 certs["date"] = certs["date"].split('.')[0] + " UTC"
216 certs["date"] = certs["date"].split('.')[0] + " UTC"
215 return certs
217 return certs
216
218
217 # implement the converter_source interface:
219 # implement the converter_source interface:
218
220
219 def getheads(self):
221 def getheads(self):
220 if not self.rev:
222 if not self.rev:
221 return self.mtnrun("leaves").splitlines()
223 return self.mtnrun("leaves").splitlines()
222 else:
224 else:
223 return [self.rev]
225 return [self.rev]
224
226
225 def getchanges(self, rev):
227 def getchanges(self, rev):
226 #revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
228 #revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
227 revision = self.mtnrun("get_revision", rev).split("\n\n")
229 revision = self.mtnrun("get_revision", rev).split("\n\n")
228 files = {}
230 files = {}
229 ignoremove = {}
231 ignoremove = {}
230 renameddirs = []
232 renameddirs = []
231 copies = {}
233 copies = {}
232 for e in revision:
234 for e in revision:
233 m = self.add_file_re.match(e)
235 m = self.add_file_re.match(e)
234 if m:
236 if m:
235 files[m.group(1)] = rev
237 files[m.group(1)] = rev
236 ignoremove[m.group(1)] = rev
238 ignoremove[m.group(1)] = rev
237 m = self.patch_re.match(e)
239 m = self.patch_re.match(e)
238 if m:
240 if m:
239 files[m.group(1)] = rev
241 files[m.group(1)] = rev
240 # Delete/rename is handled later when the convert engine
242 # Delete/rename is handled later when the convert engine
241 # discovers an IOError exception from getfile,
243 # discovers an IOError exception from getfile,
242 # but only if we add the "from" file to the list of changes.
244 # but only if we add the "from" file to the list of changes.
243 m = self.delete_re.match(e)
245 m = self.delete_re.match(e)
244 if m:
246 if m:
245 files[m.group(1)] = rev
247 files[m.group(1)] = rev
246 m = self.rename_re.match(e)
248 m = self.rename_re.match(e)
247 if m:
249 if m:
248 toname = m.group(2)
250 toname = m.group(2)
249 fromname = m.group(1)
251 fromname = m.group(1)
250 if self.mtnisfile(toname, rev):
252 if self.mtnisfile(toname, rev):
251 ignoremove[toname] = 1
253 ignoremove[toname] = 1
252 copies[toname] = fromname
254 copies[toname] = fromname
253 files[toname] = rev
255 files[toname] = rev
254 files[fromname] = rev
256 files[fromname] = rev
255 elif self.mtnisdir(toname, rev):
257 elif self.mtnisdir(toname, rev):
256 renameddirs.append((fromname, toname))
258 renameddirs.append((fromname, toname))
257
259
258 # Directory renames can be handled only once we have recorded
260 # Directory renames can be handled only once we have recorded
259 # all new files
261 # all new files
260 for fromdir, todir in renameddirs:
262 for fromdir, todir in renameddirs:
261 renamed = {}
263 renamed = {}
262 for tofile in self.files:
264 for tofile in self.files:
263 if tofile in ignoremove:
265 if tofile in ignoremove:
264 continue
266 continue
265 if tofile.startswith(todir + '/'):
267 if tofile.startswith(todir + '/'):
266 renamed[tofile] = fromdir + tofile[len(todir):]
268 renamed[tofile] = fromdir + tofile[len(todir):]
267 # Avoid chained moves like:
269 # Avoid chained moves like:
268 # d1(/a) => d3/d1(/a)
270 # d1(/a) => d3/d1(/a)
269 # d2 => d3
271 # d2 => d3
270 ignoremove[tofile] = 1
272 ignoremove[tofile] = 1
271 for tofile, fromfile in renamed.items():
273 for tofile, fromfile in renamed.items():
272 self.ui.debug (_("copying file in renamed directory "
274 self.ui.debug (_("copying file in renamed directory "
273 "from '%s' to '%s'")
275 "from '%s' to '%s'")
274 % (fromfile, tofile), '\n')
276 % (fromfile, tofile), '\n')
275 files[tofile] = rev
277 files[tofile] = rev
276 copies[tofile] = fromfile
278 copies[tofile] = fromfile
277 for fromfile in renamed.values():
279 for fromfile in renamed.values():
278 files[fromfile] = rev
280 files[fromfile] = rev
279
281
280 return (files.items(), copies)
282 return (files.items(), copies)
281
283
282 def getfile(self, name, rev):
284 def getfile(self, name, rev):
283 if not self.mtnisfile(name, rev):
285 if not self.mtnisfile(name, rev):
284 raise IOError() # file was deleted or renamed
286 raise IOError() # file was deleted or renamed
285 try:
287 try:
286 data = self.mtnrun("get_file_of", name, r=rev)
288 data = self.mtnrun("get_file_of", name, r=rev)
287 except:
289 except:
288 raise IOError() # file was deleted or renamed
290 raise IOError() # file was deleted or renamed
289 self.mtnloadmanifest(rev)
291 self.mtnloadmanifest(rev)
290 node, attr = self.files.get(name, (None, ""))
292 node, attr = self.files.get(name, (None, ""))
291 return data, attr
293 return data, attr
292
294
293 def getcommit(self, rev):
295 def getcommit(self, rev):
294 extra = {}
296 extra = {}
295 certs = self.mtngetcerts(rev)
297 certs = self.mtngetcerts(rev)
296 if certs.get('suspend') == certs["branch"]:
298 if certs.get('suspend') == certs["branch"]:
297 extra['close'] = '1'
299 extra['close'] = '1'
298 return commit(
300 return commit(
299 author=certs["author"],
301 author=certs["author"],
300 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
302 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
301 desc=certs["changelog"],
303 desc=certs["changelog"],
302 rev=rev,
304 rev=rev,
303 parents=self.mtnrun("parents", rev).splitlines(),
305 parents=self.mtnrun("parents", rev).splitlines(),
304 branch=certs["branch"],
306 branch=certs["branch"],
305 extra=extra)
307 extra=extra)
306
308
307 def gettags(self):
309 def gettags(self):
308 tags = {}
310 tags = {}
309 for e in self.mtnrun("tags").split("\n\n"):
311 for e in self.mtnrun("tags").split("\n\n"):
310 m = self.tag_re.match(e)
312 m = self.tag_re.match(e)
311 if m:
313 if m:
312 tags[m.group(1)] = m.group(2)
314 tags[m.group(1)] = m.group(2)
313 return tags
315 return tags
314
316
315 def getchangedfiles(self, rev, i):
317 def getchangedfiles(self, rev, i):
316 # This function is only needed to support --filemap
318 # This function is only needed to support --filemap
317 # ... and we don't support that
319 # ... and we don't support that
318 raise NotImplementedError()
320 raise NotImplementedError()
319
321
320 def before(self):
322 def before(self):
321 # Check if we have a new enough version to use automate stdio
323 # Check if we have a new enough version to use automate stdio
322 version = 0.0
324 version = 0.0
323 try:
325 try:
324 versionstr = self.mtnrunsingle("interface_version")
326 versionstr = self.mtnrunsingle("interface_version")
325 version = float(versionstr)
327 version = float(versionstr)
326 except Exception:
328 except Exception:
327 raise util.Abort(_("unable to determine mtn automate interface "
329 raise util.Abort(_("unable to determine mtn automate interface "
328 "version"))
330 "version"))
329
331
330 if version >= 12.0:
332 if version >= 12.0:
331 self.automatestdio = True
333 self.automatestdio = True
332 self.ui.debug("mtn automate version %s - using automate stdio\n" %
334 self.ui.debug("mtn automate version %s - using automate stdio\n" %
333 version)
335 version)
334
336
335 # launch the long-running automate stdio process
337 # launch the long-running automate stdio process
336 self.mtnwritefp, self.mtnreadfp = self._run2('automate', 'stdio',
338 self.mtnwritefp, self.mtnreadfp = self._run2('automate', 'stdio',
337 '-d', self.path)
339 '-d', self.path)
338 # read the headers
340 # read the headers
339 read = self.mtnreadfp.readline()
341 read = self.mtnreadfp.readline()
340 if read != 'format-version: 2\n':
342 if read != 'format-version: 2\n':
341 raise util.Abort(_('mtn automate stdio header unexpected: %s')
343 raise util.Abort(_('mtn automate stdio header unexpected: %s')
342 % read)
344 % read)
343 while read != '\n':
345 while read != '\n':
344 read = self.mtnreadfp.readline()
346 read = self.mtnreadfp.readline()
345 if not read:
347 if not read:
346 raise util.Abort(_("failed to reach end of mtn automate "
348 raise util.Abort(_("failed to reach end of mtn automate "
347 "stdio headers"))
349 "stdio headers"))
348 else:
350 else:
349 self.ui.debug("mtn automate version %s - not using automate stdio "
351 self.ui.debug("mtn automate version %s - not using automate stdio "
350 "(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version)
352 "(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version)
351
353
352 def after(self):
354 def after(self):
353 if self.automatestdio:
355 if self.automatestdio:
354 self.mtnwritefp.close()
356 self.mtnwritefp.close()
355 self.mtnwritefp = None
357 self.mtnwritefp = None
356 self.mtnreadfp.close()
358 self.mtnreadfp.close()
357 self.mtnreadfp = None
359 self.mtnreadfp = None
358
360
@@ -1,441 +1,441
1 # linuxserver.py - inotify status server for linux
1 # linuxserver.py - inotify status server for linux
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 of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10 from mercurial import osutil, util
10 from mercurial import osutil, util
11 import server
11 import server
12 import errno, os, select, stat, sys, time
12 import errno, os, select, stat, sys, 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 raise
18 raise
19
19
20 def walkrepodirs(dirstate, absroot):
20 def walkrepodirs(dirstate, absroot):
21 '''Iterate over all subdirectories of this repo.
21 '''Iterate over all subdirectories of this repo.
22 Exclude the .hg directory, any nested repos, and ignored dirs.'''
22 Exclude the .hg directory, any nested repos, and ignored dirs.'''
23 def walkit(dirname, top):
23 def walkit(dirname, top):
24 fullpath = server.join(absroot, dirname)
24 fullpath = server.join(absroot, dirname)
25 try:
25 try:
26 for name, kind in osutil.listdir(fullpath):
26 for name, kind in osutil.listdir(fullpath):
27 if kind == stat.S_IFDIR:
27 if kind == stat.S_IFDIR:
28 if name == '.hg':
28 if name == '.hg':
29 if not top:
29 if not top:
30 return
30 return
31 else:
31 else:
32 d = server.join(dirname, name)
32 d = server.join(dirname, name)
33 if dirstate._ignore(d):
33 if dirstate._ignore(d):
34 continue
34 continue
35 for subdir in walkit(d, False):
35 for subdir in walkit(d, False):
36 yield subdir
36 yield subdir
37 except OSError, err:
37 except OSError, err:
38 if err.errno not in server.walk_ignored_errors:
38 if err.errno not in server.walk_ignored_errors:
39 raise
39 raise
40 yield fullpath
40 yield fullpath
41
41
42 return walkit('', True)
42 return walkit('', True)
43
43
44 def _explain_watch_limit(ui, dirstate, rootabs):
44 def _explain_watch_limit(ui, dirstate, rootabs):
45 path = '/proc/sys/fs/inotify/max_user_watches'
45 path = '/proc/sys/fs/inotify/max_user_watches'
46 try:
46 try:
47 limit = int(file(path).read())
47 limit = int(util.readfile(path))
48 except IOError, err:
48 except IOError, err:
49 if err.errno != errno.ENOENT:
49 if err.errno != errno.ENOENT:
50 raise
50 raise
51 raise util.Abort(_('this system does not seem to '
51 raise util.Abort(_('this system does not seem to '
52 'support inotify'))
52 'support inotify'))
53 ui.warn(_('*** the current per-user limit on the number '
53 ui.warn(_('*** the current per-user limit on the number '
54 'of inotify watches is %s\n') % limit)
54 'of inotify watches is %s\n') % limit)
55 ui.warn(_('*** this limit is too low to watch every '
55 ui.warn(_('*** this limit is too low to watch every '
56 'directory in this repository\n'))
56 'directory in this repository\n'))
57 ui.warn(_('*** counting directories: '))
57 ui.warn(_('*** counting directories: '))
58 ndirs = len(list(walkrepodirs(dirstate, rootabs)))
58 ndirs = len(list(walkrepodirs(dirstate, rootabs)))
59 ui.warn(_('found %d\n') % ndirs)
59 ui.warn(_('found %d\n') % ndirs)
60 newlimit = min(limit, 1024)
60 newlimit = min(limit, 1024)
61 while newlimit < ((limit + ndirs) * 1.1):
61 while newlimit < ((limit + ndirs) * 1.1):
62 newlimit *= 2
62 newlimit *= 2
63 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
63 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
64 (limit, newlimit))
64 (limit, newlimit))
65 ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
65 ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
66 raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
66 raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
67 % rootabs)
67 % rootabs)
68
68
69 class pollable(object):
69 class pollable(object):
70 """
70 """
71 Interface to support polling.
71 Interface to support polling.
72 The file descriptor returned by fileno() is registered to a polling
72 The file descriptor returned by fileno() is registered to a polling
73 object.
73 object.
74 Usage:
74 Usage:
75 Every tick, check if an event has happened since the last tick:
75 Every tick, check if an event has happened since the last tick:
76 * If yes, call handle_events
76 * If yes, call handle_events
77 * If no, call handle_timeout
77 * If no, call handle_timeout
78 """
78 """
79 poll_events = select.POLLIN
79 poll_events = select.POLLIN
80 instances = {}
80 instances = {}
81 poll = select.poll()
81 poll = select.poll()
82
82
83 def fileno(self):
83 def fileno(self):
84 raise NotImplementedError
84 raise NotImplementedError
85
85
86 def handle_events(self, events):
86 def handle_events(self, events):
87 raise NotImplementedError
87 raise NotImplementedError
88
88
89 def handle_timeout(self):
89 def handle_timeout(self):
90 raise NotImplementedError
90 raise NotImplementedError
91
91
92 def shutdown(self):
92 def shutdown(self):
93 raise NotImplementedError
93 raise NotImplementedError
94
94
95 def register(self, timeout):
95 def register(self, timeout):
96 fd = self.fileno()
96 fd = self.fileno()
97
97
98 pollable.poll.register(fd, pollable.poll_events)
98 pollable.poll.register(fd, pollable.poll_events)
99 pollable.instances[fd] = self
99 pollable.instances[fd] = self
100
100
101 self.registered = True
101 self.registered = True
102 self.timeout = timeout
102 self.timeout = timeout
103
103
104 def unregister(self):
104 def unregister(self):
105 pollable.poll.unregister(self)
105 pollable.poll.unregister(self)
106 self.registered = False
106 self.registered = False
107
107
108 @classmethod
108 @classmethod
109 def run(cls):
109 def run(cls):
110 while True:
110 while True:
111 timeout = None
111 timeout = None
112 timeobj = None
112 timeobj = None
113 for obj in cls.instances.itervalues():
113 for obj in cls.instances.itervalues():
114 if obj.timeout is not None and (timeout is None
114 if obj.timeout is not None and (timeout is None
115 or obj.timeout < timeout):
115 or obj.timeout < timeout):
116 timeout, timeobj = obj.timeout, obj
116 timeout, timeobj = obj.timeout, obj
117 try:
117 try:
118 events = cls.poll.poll(timeout)
118 events = cls.poll.poll(timeout)
119 except select.error, err:
119 except select.error, err:
120 if err.args[0] == errno.EINTR:
120 if err.args[0] == errno.EINTR:
121 continue
121 continue
122 raise
122 raise
123 if events:
123 if events:
124 by_fd = {}
124 by_fd = {}
125 for fd, event in events:
125 for fd, event in events:
126 by_fd.setdefault(fd, []).append(event)
126 by_fd.setdefault(fd, []).append(event)
127
127
128 for fd, events in by_fd.iteritems():
128 for fd, events in by_fd.iteritems():
129 cls.instances[fd].handle_pollevents(events)
129 cls.instances[fd].handle_pollevents(events)
130
130
131 elif timeobj:
131 elif timeobj:
132 timeobj.handle_timeout()
132 timeobj.handle_timeout()
133
133
134 def eventaction(code):
134 def eventaction(code):
135 """
135 """
136 Decorator to help handle events in repowatcher
136 Decorator to help handle events in repowatcher
137 """
137 """
138 def decorator(f):
138 def decorator(f):
139 def wrapper(self, wpath):
139 def wrapper(self, wpath):
140 if code == 'm' and wpath in self.lastevent and \
140 if code == 'm' and wpath in self.lastevent and \
141 self.lastevent[wpath] in 'cm':
141 self.lastevent[wpath] in 'cm':
142 return
142 return
143 self.lastevent[wpath] = code
143 self.lastevent[wpath] = code
144 self.timeout = 250
144 self.timeout = 250
145
145
146 f(self, wpath)
146 f(self, wpath)
147
147
148 wrapper.func_name = f.func_name
148 wrapper.func_name = f.func_name
149 return wrapper
149 return wrapper
150 return decorator
150 return decorator
151
151
152 class repowatcher(server.repowatcher, pollable):
152 class repowatcher(server.repowatcher, pollable):
153 """
153 """
154 Watches inotify events
154 Watches inotify events
155 """
155 """
156 mask = (
156 mask = (
157 inotify.IN_ATTRIB |
157 inotify.IN_ATTRIB |
158 inotify.IN_CREATE |
158 inotify.IN_CREATE |
159 inotify.IN_DELETE |
159 inotify.IN_DELETE |
160 inotify.IN_DELETE_SELF |
160 inotify.IN_DELETE_SELF |
161 inotify.IN_MODIFY |
161 inotify.IN_MODIFY |
162 inotify.IN_MOVED_FROM |
162 inotify.IN_MOVED_FROM |
163 inotify.IN_MOVED_TO |
163 inotify.IN_MOVED_TO |
164 inotify.IN_MOVE_SELF |
164 inotify.IN_MOVE_SELF |
165 inotify.IN_ONLYDIR |
165 inotify.IN_ONLYDIR |
166 inotify.IN_UNMOUNT |
166 inotify.IN_UNMOUNT |
167 0)
167 0)
168
168
169 def __init__(self, ui, dirstate, root):
169 def __init__(self, ui, dirstate, root):
170 server.repowatcher.__init__(self, ui, dirstate, root)
170 server.repowatcher.__init__(self, ui, dirstate, root)
171
171
172 self.lastevent = {}
172 self.lastevent = {}
173 self.dirty = False
173 self.dirty = False
174 try:
174 try:
175 self.watcher = watcher.watcher()
175 self.watcher = watcher.watcher()
176 except OSError, err:
176 except OSError, err:
177 raise util.Abort(_('inotify service not available: %s') %
177 raise util.Abort(_('inotify service not available: %s') %
178 err.strerror)
178 err.strerror)
179 self.threshold = watcher.threshold(self.watcher)
179 self.threshold = watcher.threshold(self.watcher)
180 self.fileno = self.watcher.fileno
180 self.fileno = self.watcher.fileno
181 self.register(timeout=None)
181 self.register(timeout=None)
182
182
183 self.handle_timeout()
183 self.handle_timeout()
184 self.scan()
184 self.scan()
185
185
186 def event_time(self):
186 def event_time(self):
187 last = self.last_event
187 last = self.last_event
188 now = time.time()
188 now = time.time()
189 self.last_event = now
189 self.last_event = now
190
190
191 if last is None:
191 if last is None:
192 return 'start'
192 return 'start'
193 delta = now - last
193 delta = now - last
194 if delta < 5:
194 if delta < 5:
195 return '+%.3f' % delta
195 return '+%.3f' % delta
196 if delta < 50:
196 if delta < 50:
197 return '+%.2f' % delta
197 return '+%.2f' % delta
198 return '+%.1f' % delta
198 return '+%.1f' % delta
199
199
200 def add_watch(self, path, mask):
200 def add_watch(self, path, mask):
201 if not path:
201 if not path:
202 return
202 return
203 if self.watcher.path(path) is None:
203 if self.watcher.path(path) is None:
204 if self.ui.debugflag:
204 if self.ui.debugflag:
205 self.ui.note(_('watching %r\n') % path[self.prefixlen:])
205 self.ui.note(_('watching %r\n') % path[self.prefixlen:])
206 try:
206 try:
207 self.watcher.add(path, mask)
207 self.watcher.add(path, mask)
208 except OSError, err:
208 except OSError, err:
209 if err.errno in (errno.ENOENT, errno.ENOTDIR):
209 if err.errno in (errno.ENOENT, errno.ENOTDIR):
210 return
210 return
211 if err.errno != errno.ENOSPC:
211 if err.errno != errno.ENOSPC:
212 raise
212 raise
213 _explain_watch_limit(self.ui, self.dirstate, self.wprefix)
213 _explain_watch_limit(self.ui, self.dirstate, self.wprefix)
214
214
215 def setup(self):
215 def setup(self):
216 self.ui.note(_('watching directories under %r\n') % self.wprefix)
216 self.ui.note(_('watching directories under %r\n') % self.wprefix)
217 self.add_watch(self.wprefix + '.hg', inotify.IN_DELETE)
217 self.add_watch(self.wprefix + '.hg', inotify.IN_DELETE)
218
218
219 def scan(self, topdir=''):
219 def scan(self, topdir=''):
220 ds = self.dirstate._map.copy()
220 ds = self.dirstate._map.copy()
221 self.add_watch(server.join(self.wprefix, topdir), self.mask)
221 self.add_watch(server.join(self.wprefix, topdir), self.mask)
222 for root, dirs, files in server.walk(self.dirstate, self.wprefix,
222 for root, dirs, files in server.walk(self.dirstate, self.wprefix,
223 topdir):
223 topdir):
224 for d in dirs:
224 for d in dirs:
225 self.add_watch(server.join(root, d), self.mask)
225 self.add_watch(server.join(root, d), self.mask)
226 wroot = root[self.prefixlen:]
226 wroot = root[self.prefixlen:]
227 for fn in files:
227 for fn in files:
228 wfn = server.join(wroot, fn)
228 wfn = server.join(wroot, fn)
229 self.updatefile(wfn, self.getstat(wfn))
229 self.updatefile(wfn, self.getstat(wfn))
230 ds.pop(wfn, None)
230 ds.pop(wfn, None)
231 wtopdir = topdir
231 wtopdir = topdir
232 if wtopdir and wtopdir[-1] != '/':
232 if wtopdir and wtopdir[-1] != '/':
233 wtopdir += '/'
233 wtopdir += '/'
234 for wfn, state in ds.iteritems():
234 for wfn, state in ds.iteritems():
235 if not wfn.startswith(wtopdir):
235 if not wfn.startswith(wtopdir):
236 continue
236 continue
237 try:
237 try:
238 st = self.stat(wfn)
238 st = self.stat(wfn)
239 except OSError:
239 except OSError:
240 status = state[0]
240 status = state[0]
241 self.deletefile(wfn, status)
241 self.deletefile(wfn, status)
242 else:
242 else:
243 self.updatefile(wfn, st)
243 self.updatefile(wfn, st)
244 self.check_deleted('!')
244 self.check_deleted('!')
245 self.check_deleted('r')
245 self.check_deleted('r')
246
246
247 @eventaction('c')
247 @eventaction('c')
248 def created(self, wpath):
248 def created(self, wpath):
249 if wpath == '.hgignore':
249 if wpath == '.hgignore':
250 self.update_hgignore()
250 self.update_hgignore()
251 try:
251 try:
252 st = self.stat(wpath)
252 st = self.stat(wpath)
253 if stat.S_ISREG(st[0]) or stat.S_ISLNK(st[0]):
253 if stat.S_ISREG(st[0]) or stat.S_ISLNK(st[0]):
254 self.updatefile(wpath, st)
254 self.updatefile(wpath, st)
255 except OSError:
255 except OSError:
256 pass
256 pass
257
257
258 @eventaction('m')
258 @eventaction('m')
259 def modified(self, wpath):
259 def modified(self, wpath):
260 if wpath == '.hgignore':
260 if wpath == '.hgignore':
261 self.update_hgignore()
261 self.update_hgignore()
262 try:
262 try:
263 st = self.stat(wpath)
263 st = self.stat(wpath)
264 if stat.S_ISREG(st[0]):
264 if stat.S_ISREG(st[0]):
265 if self.dirstate[wpath] in 'lmn':
265 if self.dirstate[wpath] in 'lmn':
266 self.updatefile(wpath, st)
266 self.updatefile(wpath, st)
267 except OSError:
267 except OSError:
268 pass
268 pass
269
269
270 @eventaction('d')
270 @eventaction('d')
271 def deleted(self, wpath):
271 def deleted(self, wpath):
272 if wpath == '.hgignore':
272 if wpath == '.hgignore':
273 self.update_hgignore()
273 self.update_hgignore()
274 elif wpath.startswith('.hg/'):
274 elif wpath.startswith('.hg/'):
275 return
275 return
276
276
277 self.deletefile(wpath, self.dirstate[wpath])
277 self.deletefile(wpath, self.dirstate[wpath])
278
278
279 def process_create(self, wpath, evt):
279 def process_create(self, wpath, evt):
280 if self.ui.debugflag:
280 if self.ui.debugflag:
281 self.ui.note(_('%s event: created %s\n') %
281 self.ui.note(_('%s event: created %s\n') %
282 (self.event_time(), wpath))
282 (self.event_time(), wpath))
283
283
284 if evt.mask & inotify.IN_ISDIR:
284 if evt.mask & inotify.IN_ISDIR:
285 self.scan(wpath)
285 self.scan(wpath)
286 else:
286 else:
287 self.created(wpath)
287 self.created(wpath)
288
288
289 def process_delete(self, wpath, evt):
289 def process_delete(self, wpath, evt):
290 if self.ui.debugflag:
290 if self.ui.debugflag:
291 self.ui.note(_('%s event: deleted %s\n') %
291 self.ui.note(_('%s event: deleted %s\n') %
292 (self.event_time(), wpath))
292 (self.event_time(), wpath))
293
293
294 if evt.mask & inotify.IN_ISDIR:
294 if evt.mask & inotify.IN_ISDIR:
295 tree = self.tree.dir(wpath)
295 tree = self.tree.dir(wpath)
296 todelete = [wfn for wfn, ignore in tree.walk('?')]
296 todelete = [wfn for wfn, ignore in tree.walk('?')]
297 for fn in todelete:
297 for fn in todelete:
298 self.deletefile(fn, '?')
298 self.deletefile(fn, '?')
299 self.scan(wpath)
299 self.scan(wpath)
300 else:
300 else:
301 self.deleted(wpath)
301 self.deleted(wpath)
302
302
303 def process_modify(self, wpath, evt):
303 def process_modify(self, wpath, evt):
304 if self.ui.debugflag:
304 if self.ui.debugflag:
305 self.ui.note(_('%s event: modified %s\n') %
305 self.ui.note(_('%s event: modified %s\n') %
306 (self.event_time(), wpath))
306 (self.event_time(), wpath))
307
307
308 if not (evt.mask & inotify.IN_ISDIR):
308 if not (evt.mask & inotify.IN_ISDIR):
309 self.modified(wpath)
309 self.modified(wpath)
310
310
311 def process_unmount(self, evt):
311 def process_unmount(self, evt):
312 self.ui.warn(_('filesystem containing %s was unmounted\n') %
312 self.ui.warn(_('filesystem containing %s was unmounted\n') %
313 evt.fullpath)
313 evt.fullpath)
314 sys.exit(0)
314 sys.exit(0)
315
315
316 def handle_pollevents(self, events):
316 def handle_pollevents(self, events):
317 if self.ui.debugflag:
317 if self.ui.debugflag:
318 self.ui.note(_('%s readable: %d bytes\n') %
318 self.ui.note(_('%s readable: %d bytes\n') %
319 (self.event_time(), self.threshold.readable()))
319 (self.event_time(), self.threshold.readable()))
320 if not self.threshold():
320 if not self.threshold():
321 if self.registered:
321 if self.registered:
322 if self.ui.debugflag:
322 if self.ui.debugflag:
323 self.ui.note(_('%s below threshold - unhooking\n') %
323 self.ui.note(_('%s below threshold - unhooking\n') %
324 (self.event_time()))
324 (self.event_time()))
325 self.unregister()
325 self.unregister()
326 self.timeout = 250
326 self.timeout = 250
327 else:
327 else:
328 self.read_events()
328 self.read_events()
329
329
330 def read_events(self, bufsize=None):
330 def read_events(self, bufsize=None):
331 events = self.watcher.read(bufsize)
331 events = self.watcher.read(bufsize)
332 if self.ui.debugflag:
332 if self.ui.debugflag:
333 self.ui.note(_('%s reading %d events\n') %
333 self.ui.note(_('%s reading %d events\n') %
334 (self.event_time(), len(events)))
334 (self.event_time(), len(events)))
335 for evt in events:
335 for evt in events:
336 if evt.fullpath == self.wprefix[:-1]:
336 if evt.fullpath == self.wprefix[:-1]:
337 # events on the root of the repository
337 # events on the root of the repository
338 # itself, e.g. permission changes or repository move
338 # itself, e.g. permission changes or repository move
339 continue
339 continue
340 assert evt.fullpath.startswith(self.wprefix)
340 assert evt.fullpath.startswith(self.wprefix)
341 wpath = evt.fullpath[self.prefixlen:]
341 wpath = evt.fullpath[self.prefixlen:]
342
342
343 # paths have been normalized, wpath never ends with a '/'
343 # paths have been normalized, wpath never ends with a '/'
344
344
345 if wpath.startswith('.hg/') and evt.mask & inotify.IN_ISDIR:
345 if wpath.startswith('.hg/') and evt.mask & inotify.IN_ISDIR:
346 # ignore subdirectories of .hg/ (merge, patches...)
346 # ignore subdirectories of .hg/ (merge, patches...)
347 continue
347 continue
348 if wpath == ".hg/wlock":
348 if wpath == ".hg/wlock":
349 if evt.mask & inotify.IN_DELETE:
349 if evt.mask & inotify.IN_DELETE:
350 self.dirstate.invalidate()
350 self.dirstate.invalidate()
351 self.dirty = False
351 self.dirty = False
352 self.scan()
352 self.scan()
353 elif evt.mask & inotify.IN_CREATE:
353 elif evt.mask & inotify.IN_CREATE:
354 self.dirty = True
354 self.dirty = True
355 else:
355 else:
356 if self.dirty:
356 if self.dirty:
357 continue
357 continue
358
358
359 if evt.mask & inotify.IN_UNMOUNT:
359 if evt.mask & inotify.IN_UNMOUNT:
360 self.process_unmount(wpath, evt)
360 self.process_unmount(wpath, evt)
361 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
361 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
362 self.process_modify(wpath, evt)
362 self.process_modify(wpath, evt)
363 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
363 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
364 inotify.IN_MOVED_FROM):
364 inotify.IN_MOVED_FROM):
365 self.process_delete(wpath, evt)
365 self.process_delete(wpath, evt)
366 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
366 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
367 self.process_create(wpath, evt)
367 self.process_create(wpath, evt)
368
368
369 self.lastevent.clear()
369 self.lastevent.clear()
370
370
371 def handle_timeout(self):
371 def handle_timeout(self):
372 if not self.registered:
372 if not self.registered:
373 if self.ui.debugflag:
373 if self.ui.debugflag:
374 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
374 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
375 (self.event_time(), self.threshold.readable()))
375 (self.event_time(), self.threshold.readable()))
376 self.read_events(0)
376 self.read_events(0)
377 self.register(timeout=None)
377 self.register(timeout=None)
378
378
379 self.timeout = None
379 self.timeout = None
380
380
381 def shutdown(self):
381 def shutdown(self):
382 self.watcher.close()
382 self.watcher.close()
383
383
384 def debug(self):
384 def debug(self):
385 """
385 """
386 Returns a sorted list of relatives paths currently watched,
386 Returns a sorted list of relatives paths currently watched,
387 for debugging purposes.
387 for debugging purposes.
388 """
388 """
389 return sorted(tuple[0][self.prefixlen:] for tuple in self.watcher)
389 return sorted(tuple[0][self.prefixlen:] for tuple in self.watcher)
390
390
391 class socketlistener(server.socketlistener, pollable):
391 class socketlistener(server.socketlistener, pollable):
392 """
392 """
393 Listens for client queries on unix socket inotify.sock
393 Listens for client queries on unix socket inotify.sock
394 """
394 """
395 def __init__(self, ui, root, repowatcher, timeout):
395 def __init__(self, ui, root, repowatcher, timeout):
396 server.socketlistener.__init__(self, ui, root, repowatcher, timeout)
396 server.socketlistener.__init__(self, ui, root, repowatcher, timeout)
397 self.register(timeout=timeout)
397 self.register(timeout=timeout)
398
398
399 def handle_timeout(self):
399 def handle_timeout(self):
400 raise server.TimeoutException
400 raise server.TimeoutException
401
401
402 def handle_pollevents(self, events):
402 def handle_pollevents(self, events):
403 for e in events:
403 for e in events:
404 self.accept_connection()
404 self.accept_connection()
405
405
406 def shutdown(self):
406 def shutdown(self):
407 self.sock.close()
407 self.sock.close()
408 try:
408 try:
409 os.unlink(self.sockpath)
409 os.unlink(self.sockpath)
410 if self.realsockpath:
410 if self.realsockpath:
411 os.unlink(self.realsockpath)
411 os.unlink(self.realsockpath)
412 os.rmdir(os.path.dirname(self.realsockpath))
412 os.rmdir(os.path.dirname(self.realsockpath))
413 except OSError, err:
413 except OSError, err:
414 if err.errno != errno.ENOENT:
414 if err.errno != errno.ENOENT:
415 raise
415 raise
416
416
417 def answer_stat_query(self, cs):
417 def answer_stat_query(self, cs):
418 if self.repowatcher.timeout:
418 if self.repowatcher.timeout:
419 # We got a query while a rescan is pending. Make sure we
419 # We got a query while a rescan is pending. Make sure we
420 # rescan before responding, or we could give back a wrong
420 # rescan before responding, or we could give back a wrong
421 # answer.
421 # answer.
422 self.repowatcher.handle_timeout()
422 self.repowatcher.handle_timeout()
423 return server.socketlistener.answer_stat_query(self, cs)
423 return server.socketlistener.answer_stat_query(self, cs)
424
424
425 class master(object):
425 class master(object):
426 def __init__(self, ui, dirstate, root, timeout=None):
426 def __init__(self, ui, dirstate, root, timeout=None):
427 self.ui = ui
427 self.ui = ui
428 self.repowatcher = repowatcher(ui, dirstate, root)
428 self.repowatcher = repowatcher(ui, dirstate, root)
429 self.socketlistener = socketlistener(ui, root, self.repowatcher,
429 self.socketlistener = socketlistener(ui, root, self.repowatcher,
430 timeout)
430 timeout)
431
431
432 def shutdown(self):
432 def shutdown(self):
433 for obj in pollable.instances.itervalues():
433 for obj in pollable.instances.itervalues():
434 obj.shutdown()
434 obj.shutdown()
435
435
436 def run(self):
436 def run(self):
437 self.repowatcher.setup()
437 self.repowatcher.setup()
438 self.ui.note(_('finished setup\n'))
438 self.ui.note(_('finished setup\n'))
439 if os.getenv('TIME_STARTUP'):
439 if os.getenv('TIME_STARTUP'):
440 sys.exit(0)
440 sys.exit(0)
441 pollable.run()
441 pollable.run()
General Comments 0
You need to be logged in to leave comments. Login now