##// END OF EJS Templates
fsmonitor: change the distance calculation...
Eamonn Kent -
r34565:b79f5942 default
parent child Browse files
Show More
@@ -1,729 +1,731 b''
1 # __init__.py - fsmonitor initialization and overrides
1 # __init__.py - fsmonitor initialization and overrides
2 #
2 #
3 # Copyright 2013-2016 Facebook, Inc.
3 # Copyright 2013-2016 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''Faster status operations with the Watchman file monitor (EXPERIMENTAL)
8 '''Faster status operations with the Watchman file monitor (EXPERIMENTAL)
9
9
10 Integrates the file-watching program Watchman with Mercurial to produce faster
10 Integrates the file-watching program Watchman with Mercurial to produce faster
11 status results.
11 status results.
12
12
13 On a particular Linux system, for a real-world repository with over 400,000
13 On a particular Linux system, for a real-world repository with over 400,000
14 files hosted on ext4, vanilla `hg status` takes 1.3 seconds. On the same
14 files hosted on ext4, vanilla `hg status` takes 1.3 seconds. On the same
15 system, with fsmonitor it takes about 0.3 seconds.
15 system, with fsmonitor it takes about 0.3 seconds.
16
16
17 fsmonitor requires no configuration -- it will tell Watchman about your
17 fsmonitor requires no configuration -- it will tell Watchman about your
18 repository as necessary. You'll need to install Watchman from
18 repository as necessary. You'll need to install Watchman from
19 https://facebook.github.io/watchman/ and make sure it is in your PATH.
19 https://facebook.github.io/watchman/ and make sure it is in your PATH.
20
20
21 The following configuration options exist:
21 The following configuration options exist:
22
22
23 ::
23 ::
24
24
25 [fsmonitor]
25 [fsmonitor]
26 mode = {off, on, paranoid}
26 mode = {off, on, paranoid}
27
27
28 When `mode = off`, fsmonitor will disable itself (similar to not loading the
28 When `mode = off`, fsmonitor will disable itself (similar to not loading the
29 extension at all). When `mode = on`, fsmonitor will be enabled (the default).
29 extension at all). When `mode = on`, fsmonitor will be enabled (the default).
30 When `mode = paranoid`, fsmonitor will query both Watchman and the filesystem,
30 When `mode = paranoid`, fsmonitor will query both Watchman and the filesystem,
31 and ensure that the results are consistent.
31 and ensure that the results are consistent.
32
32
33 ::
33 ::
34
34
35 [fsmonitor]
35 [fsmonitor]
36 timeout = (float)
36 timeout = (float)
37
37
38 A value, in seconds, that determines how long fsmonitor will wait for Watchman
38 A value, in seconds, that determines how long fsmonitor will wait for Watchman
39 to return results. Defaults to `2.0`.
39 to return results. Defaults to `2.0`.
40
40
41 ::
41 ::
42
42
43 [fsmonitor]
43 [fsmonitor]
44 blacklistusers = (list of userids)
44 blacklistusers = (list of userids)
45
45
46 A list of usernames for which fsmonitor will disable itself altogether.
46 A list of usernames for which fsmonitor will disable itself altogether.
47
47
48 ::
48 ::
49
49
50 [fsmonitor]
50 [fsmonitor]
51 walk_on_invalidate = (boolean)
51 walk_on_invalidate = (boolean)
52
52
53 Whether or not to walk the whole repo ourselves when our cached state has been
53 Whether or not to walk the whole repo ourselves when our cached state has been
54 invalidated, for example when Watchman has been restarted or .hgignore rules
54 invalidated, for example when Watchman has been restarted or .hgignore rules
55 have been changed. Walking the repo in that case can result in competing for
55 have been changed. Walking the repo in that case can result in competing for
56 I/O with Watchman. For large repos it is recommended to set this value to
56 I/O with Watchman. For large repos it is recommended to set this value to
57 false. You may wish to set this to true if you have a very fast filesystem
57 false. You may wish to set this to true if you have a very fast filesystem
58 that can outpace the IPC overhead of getting the result data for the full repo
58 that can outpace the IPC overhead of getting the result data for the full repo
59 from Watchman. Defaults to false.
59 from Watchman. Defaults to false.
60
60
61 fsmonitor is incompatible with the largefiles and eol extensions, and
61 fsmonitor is incompatible with the largefiles and eol extensions, and
62 will disable itself if any of those are active.
62 will disable itself if any of those are active.
63
63
64 '''
64 '''
65
65
66 # Platforms Supported
66 # Platforms Supported
67 # ===================
67 # ===================
68 #
68 #
69 # **Linux:** *Stable*. Watchman and fsmonitor are both known to work reliably,
69 # **Linux:** *Stable*. Watchman and fsmonitor are both known to work reliably,
70 # even under severe loads.
70 # even under severe loads.
71 #
71 #
72 # **Mac OS X:** *Stable*. The Mercurial test suite passes with fsmonitor
72 # **Mac OS X:** *Stable*. The Mercurial test suite passes with fsmonitor
73 # turned on, on case-insensitive HFS+. There has been a reasonable amount of
73 # turned on, on case-insensitive HFS+. There has been a reasonable amount of
74 # user testing under normal loads.
74 # user testing under normal loads.
75 #
75 #
76 # **Solaris, BSD:** *Alpha*. watchman and fsmonitor are believed to work, but
76 # **Solaris, BSD:** *Alpha*. watchman and fsmonitor are believed to work, but
77 # very little testing has been done.
77 # very little testing has been done.
78 #
78 #
79 # **Windows:** *Alpha*. Not in a release version of watchman or fsmonitor yet.
79 # **Windows:** *Alpha*. Not in a release version of watchman or fsmonitor yet.
80 #
80 #
81 # Known Issues
81 # Known Issues
82 # ============
82 # ============
83 #
83 #
84 # * fsmonitor will disable itself if any of the following extensions are
84 # * fsmonitor will disable itself if any of the following extensions are
85 # enabled: largefiles, inotify, eol; or if the repository has subrepos.
85 # enabled: largefiles, inotify, eol; or if the repository has subrepos.
86 # * fsmonitor will produce incorrect results if nested repos that are not
86 # * fsmonitor will produce incorrect results if nested repos that are not
87 # subrepos exist. *Workaround*: add nested repo paths to your `.hgignore`.
87 # subrepos exist. *Workaround*: add nested repo paths to your `.hgignore`.
88 #
88 #
89 # The issues related to nested repos and subrepos are probably not fundamental
89 # The issues related to nested repos and subrepos are probably not fundamental
90 # ones. Patches to fix them are welcome.
90 # ones. Patches to fix them are welcome.
91
91
92 from __future__ import absolute_import
92 from __future__ import absolute_import
93
93
94 import codecs
94 import codecs
95 import hashlib
95 import hashlib
96 import os
96 import os
97 import stat
97 import stat
98 import sys
98 import sys
99
99
100 from mercurial.i18n import _
100 from mercurial.i18n import _
101 from mercurial import (
101 from mercurial import (
102 context,
102 context,
103 encoding,
103 encoding,
104 error,
104 error,
105 extensions,
105 extensions,
106 localrepo,
106 localrepo,
107 merge,
107 merge,
108 pathutil,
108 pathutil,
109 pycompat,
109 pycompat,
110 registrar,
110 registrar,
111 scmutil,
111 scmutil,
112 util,
112 util,
113 )
113 )
114 from mercurial import match as matchmod
114 from mercurial import match as matchmod
115
115
116 from . import (
116 from . import (
117 pywatchman,
117 pywatchman,
118 state,
118 state,
119 watchmanclient,
119 watchmanclient,
120 )
120 )
121
121
122 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
122 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
123 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
123 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
124 # be specifying the version(s) of Mercurial they are tested with, or
124 # be specifying the version(s) of Mercurial they are tested with, or
125 # leave the attribute unspecified.
125 # leave the attribute unspecified.
126 testedwith = 'ships-with-hg-core'
126 testedwith = 'ships-with-hg-core'
127
127
128 configtable = {}
128 configtable = {}
129 configitem = registrar.configitem(configtable)
129 configitem = registrar.configitem(configtable)
130
130
131 configitem('fsmonitor', 'mode',
131 configitem('fsmonitor', 'mode',
132 default='on',
132 default='on',
133 )
133 )
134 configitem('fsmonitor', 'walk_on_invalidate',
134 configitem('fsmonitor', 'walk_on_invalidate',
135 default=False,
135 default=False,
136 )
136 )
137 configitem('fsmonitor', 'timeout',
137 configitem('fsmonitor', 'timeout',
138 default='2',
138 default='2',
139 )
139 )
140 configitem('fsmonitor', 'blacklistusers',
140 configitem('fsmonitor', 'blacklistusers',
141 default=list,
141 default=list,
142 )
142 )
143
143
144 # This extension is incompatible with the following blacklisted extensions
144 # This extension is incompatible with the following blacklisted extensions
145 # and will disable itself when encountering one of these:
145 # and will disable itself when encountering one of these:
146 _blacklist = ['largefiles', 'eol']
146 _blacklist = ['largefiles', 'eol']
147
147
148 def _handleunavailable(ui, state, ex):
148 def _handleunavailable(ui, state, ex):
149 """Exception handler for Watchman interaction exceptions"""
149 """Exception handler for Watchman interaction exceptions"""
150 if isinstance(ex, watchmanclient.Unavailable):
150 if isinstance(ex, watchmanclient.Unavailable):
151 if ex.warn:
151 if ex.warn:
152 ui.warn(str(ex) + '\n')
152 ui.warn(str(ex) + '\n')
153 if ex.invalidate:
153 if ex.invalidate:
154 state.invalidate()
154 state.invalidate()
155 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
155 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
156 else:
156 else:
157 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
157 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
158
158
159 def _hashignore(ignore):
159 def _hashignore(ignore):
160 """Calculate hash for ignore patterns and filenames
160 """Calculate hash for ignore patterns and filenames
161
161
162 If this information changes between Mercurial invocations, we can't
162 If this information changes between Mercurial invocations, we can't
163 rely on Watchman information anymore and have to re-scan the working
163 rely on Watchman information anymore and have to re-scan the working
164 copy.
164 copy.
165
165
166 """
166 """
167 sha1 = hashlib.sha1()
167 sha1 = hashlib.sha1()
168 sha1.update(repr(ignore))
168 sha1.update(repr(ignore))
169 return sha1.hexdigest()
169 return sha1.hexdigest()
170
170
171 _watchmanencoding = pywatchman.encoding.get_local_encoding()
171 _watchmanencoding = pywatchman.encoding.get_local_encoding()
172 _fsencoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
172 _fsencoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
173 _fixencoding = codecs.lookup(_watchmanencoding) != codecs.lookup(_fsencoding)
173 _fixencoding = codecs.lookup(_watchmanencoding) != codecs.lookup(_fsencoding)
174
174
175 def _watchmantofsencoding(path):
175 def _watchmantofsencoding(path):
176 """Fix path to match watchman and local filesystem encoding
176 """Fix path to match watchman and local filesystem encoding
177
177
178 watchman's paths encoding can differ from filesystem encoding. For example,
178 watchman's paths encoding can differ from filesystem encoding. For example,
179 on Windows, it's always utf-8.
179 on Windows, it's always utf-8.
180 """
180 """
181 try:
181 try:
182 decoded = path.decode(_watchmanencoding)
182 decoded = path.decode(_watchmanencoding)
183 except UnicodeDecodeError as e:
183 except UnicodeDecodeError as e:
184 raise error.Abort(str(e), hint='watchman encoding error')
184 raise error.Abort(str(e), hint='watchman encoding error')
185
185
186 try:
186 try:
187 encoded = decoded.encode(_fsencoding, 'strict')
187 encoded = decoded.encode(_fsencoding, 'strict')
188 except UnicodeEncodeError as e:
188 except UnicodeEncodeError as e:
189 raise error.Abort(str(e))
189 raise error.Abort(str(e))
190
190
191 return encoded
191 return encoded
192
192
193 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
193 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
194 '''Replacement for dirstate.walk, hooking into Watchman.
194 '''Replacement for dirstate.walk, hooking into Watchman.
195
195
196 Whenever full is False, ignored is False, and the Watchman client is
196 Whenever full is False, ignored is False, and the Watchman client is
197 available, use Watchman combined with saved state to possibly return only a
197 available, use Watchman combined with saved state to possibly return only a
198 subset of files.'''
198 subset of files.'''
199 def bail():
199 def bail():
200 return orig(match, subrepos, unknown, ignored, full=True)
200 return orig(match, subrepos, unknown, ignored, full=True)
201
201
202 if full or ignored or not self._watchmanclient.available():
202 if full or ignored or not self._watchmanclient.available():
203 return bail()
203 return bail()
204 state = self._fsmonitorstate
204 state = self._fsmonitorstate
205 clock, ignorehash, notefiles = state.get()
205 clock, ignorehash, notefiles = state.get()
206 if not clock:
206 if not clock:
207 if state.walk_on_invalidate:
207 if state.walk_on_invalidate:
208 return bail()
208 return bail()
209 # Initial NULL clock value, see
209 # Initial NULL clock value, see
210 # https://facebook.github.io/watchman/docs/clockspec.html
210 # https://facebook.github.io/watchman/docs/clockspec.html
211 clock = 'c:0:0'
211 clock = 'c:0:0'
212 notefiles = []
212 notefiles = []
213
213
214 def fwarn(f, msg):
214 def fwarn(f, msg):
215 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
215 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
216 return False
216 return False
217
217
218 def badtype(mode):
218 def badtype(mode):
219 kind = _('unknown')
219 kind = _('unknown')
220 if stat.S_ISCHR(mode):
220 if stat.S_ISCHR(mode):
221 kind = _('character device')
221 kind = _('character device')
222 elif stat.S_ISBLK(mode):
222 elif stat.S_ISBLK(mode):
223 kind = _('block device')
223 kind = _('block device')
224 elif stat.S_ISFIFO(mode):
224 elif stat.S_ISFIFO(mode):
225 kind = _('fifo')
225 kind = _('fifo')
226 elif stat.S_ISSOCK(mode):
226 elif stat.S_ISSOCK(mode):
227 kind = _('socket')
227 kind = _('socket')
228 elif stat.S_ISDIR(mode):
228 elif stat.S_ISDIR(mode):
229 kind = _('directory')
229 kind = _('directory')
230 return _('unsupported file type (type is %s)') % kind
230 return _('unsupported file type (type is %s)') % kind
231
231
232 ignore = self._ignore
232 ignore = self._ignore
233 dirignore = self._dirignore
233 dirignore = self._dirignore
234 if unknown:
234 if unknown:
235 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
235 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
236 # ignore list changed -- can't rely on Watchman state any more
236 # ignore list changed -- can't rely on Watchman state any more
237 if state.walk_on_invalidate:
237 if state.walk_on_invalidate:
238 return bail()
238 return bail()
239 notefiles = []
239 notefiles = []
240 clock = 'c:0:0'
240 clock = 'c:0:0'
241 else:
241 else:
242 # always ignore
242 # always ignore
243 ignore = util.always
243 ignore = util.always
244 dirignore = util.always
244 dirignore = util.always
245
245
246 matchfn = match.matchfn
246 matchfn = match.matchfn
247 matchalways = match.always()
247 matchalways = match.always()
248 dmap = self._map
248 dmap = self._map
249 nonnormalset = getattr(self, '_nonnormalset', None)
249 nonnormalset = getattr(self, '_nonnormalset', None)
250
250
251 copymap = self._map.copymap
251 copymap = self._map.copymap
252 getkind = stat.S_IFMT
252 getkind = stat.S_IFMT
253 dirkind = stat.S_IFDIR
253 dirkind = stat.S_IFDIR
254 regkind = stat.S_IFREG
254 regkind = stat.S_IFREG
255 lnkkind = stat.S_IFLNK
255 lnkkind = stat.S_IFLNK
256 join = self._join
256 join = self._join
257 normcase = util.normcase
257 normcase = util.normcase
258 fresh_instance = False
258 fresh_instance = False
259
259
260 exact = skipstep3 = False
260 exact = skipstep3 = False
261 if match.isexact(): # match.exact
261 if match.isexact(): # match.exact
262 exact = True
262 exact = True
263 dirignore = util.always # skip step 2
263 dirignore = util.always # skip step 2
264 elif match.prefix(): # match.match, no patterns
264 elif match.prefix(): # match.match, no patterns
265 skipstep3 = True
265 skipstep3 = True
266
266
267 if not exact and self._checkcase:
267 if not exact and self._checkcase:
268 # note that even though we could receive directory entries, we're only
268 # note that even though we could receive directory entries, we're only
269 # interested in checking if a file with the same name exists. So only
269 # interested in checking if a file with the same name exists. So only
270 # normalize files if possible.
270 # normalize files if possible.
271 normalize = self._normalizefile
271 normalize = self._normalizefile
272 skipstep3 = False
272 skipstep3 = False
273 else:
273 else:
274 normalize = None
274 normalize = None
275
275
276 # step 1: find all explicit files
276 # step 1: find all explicit files
277 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
277 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
278
278
279 skipstep3 = skipstep3 and not (work or dirsnotfound)
279 skipstep3 = skipstep3 and not (work or dirsnotfound)
280 work = [d for d in work if not dirignore(d[0])]
280 work = [d for d in work if not dirignore(d[0])]
281
281
282 if not work and (exact or skipstep3):
282 if not work and (exact or skipstep3):
283 for s in subrepos:
283 for s in subrepos:
284 del results[s]
284 del results[s]
285 del results['.hg']
285 del results['.hg']
286 return results
286 return results
287
287
288 # step 2: query Watchman
288 # step 2: query Watchman
289 try:
289 try:
290 # Use the user-configured timeout for the query.
290 # Use the user-configured timeout for the query.
291 # Add a little slack over the top of the user query to allow for
291 # Add a little slack over the top of the user query to allow for
292 # overheads while transferring the data
292 # overheads while transferring the data
293 self._watchmanclient.settimeout(state.timeout + 0.1)
293 self._watchmanclient.settimeout(state.timeout + 0.1)
294 result = self._watchmanclient.command('query', {
294 result = self._watchmanclient.command('query', {
295 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
295 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
296 'since': clock,
296 'since': clock,
297 'expression': [
297 'expression': [
298 'not', [
298 'not', [
299 'anyof', ['dirname', '.hg'],
299 'anyof', ['dirname', '.hg'],
300 ['name', '.hg', 'wholename']
300 ['name', '.hg', 'wholename']
301 ]
301 ]
302 ],
302 ],
303 'sync_timeout': int(state.timeout * 1000),
303 'sync_timeout': int(state.timeout * 1000),
304 'empty_on_fresh_instance': state.walk_on_invalidate,
304 'empty_on_fresh_instance': state.walk_on_invalidate,
305 })
305 })
306 except Exception as ex:
306 except Exception as ex:
307 _handleunavailable(self._ui, state, ex)
307 _handleunavailable(self._ui, state, ex)
308 self._watchmanclient.clearconnection()
308 self._watchmanclient.clearconnection()
309 return bail()
309 return bail()
310 else:
310 else:
311 # We need to propagate the last observed clock up so that we
311 # We need to propagate the last observed clock up so that we
312 # can use it for our next query
312 # can use it for our next query
313 state.setlastclock(result['clock'])
313 state.setlastclock(result['clock'])
314 if result['is_fresh_instance']:
314 if result['is_fresh_instance']:
315 if state.walk_on_invalidate:
315 if state.walk_on_invalidate:
316 state.invalidate()
316 state.invalidate()
317 return bail()
317 return bail()
318 fresh_instance = True
318 fresh_instance = True
319 # Ignore any prior noteable files from the state info
319 # Ignore any prior noteable files from the state info
320 notefiles = []
320 notefiles = []
321
321
322 # for file paths which require normalization and we encounter a case
322 # for file paths which require normalization and we encounter a case
323 # collision, we store our own foldmap
323 # collision, we store our own foldmap
324 if normalize:
324 if normalize:
325 foldmap = dict((normcase(k), k) for k in results)
325 foldmap = dict((normcase(k), k) for k in results)
326
326
327 switch_slashes = pycompat.ossep == '\\'
327 switch_slashes = pycompat.ossep == '\\'
328 # The order of the results is, strictly speaking, undefined.
328 # The order of the results is, strictly speaking, undefined.
329 # For case changes on a case insensitive filesystem we may receive
329 # For case changes on a case insensitive filesystem we may receive
330 # two entries, one with exists=True and another with exists=False.
330 # two entries, one with exists=True and another with exists=False.
331 # The exists=True entries in the same response should be interpreted
331 # The exists=True entries in the same response should be interpreted
332 # as being happens-after the exists=False entries due to the way that
332 # as being happens-after the exists=False entries due to the way that
333 # Watchman tracks files. We use this property to reconcile deletes
333 # Watchman tracks files. We use this property to reconcile deletes
334 # for name case changes.
334 # for name case changes.
335 for entry in result['files']:
335 for entry in result['files']:
336 fname = entry['name']
336 fname = entry['name']
337 if _fixencoding:
337 if _fixencoding:
338 fname = _watchmantofsencoding(fname)
338 fname = _watchmantofsencoding(fname)
339 if switch_slashes:
339 if switch_slashes:
340 fname = fname.replace('\\', '/')
340 fname = fname.replace('\\', '/')
341 if normalize:
341 if normalize:
342 normed = normcase(fname)
342 normed = normcase(fname)
343 fname = normalize(fname, True, True)
343 fname = normalize(fname, True, True)
344 foldmap[normed] = fname
344 foldmap[normed] = fname
345 fmode = entry['mode']
345 fmode = entry['mode']
346 fexists = entry['exists']
346 fexists = entry['exists']
347 kind = getkind(fmode)
347 kind = getkind(fmode)
348
348
349 if not fexists:
349 if not fexists:
350 # if marked as deleted and we don't already have a change
350 # if marked as deleted and we don't already have a change
351 # record, mark it as deleted. If we already have an entry
351 # record, mark it as deleted. If we already have an entry
352 # for fname then it was either part of walkexplicit or was
352 # for fname then it was either part of walkexplicit or was
353 # an earlier result that was a case change
353 # an earlier result that was a case change
354 if fname not in results and fname in dmap and (
354 if fname not in results and fname in dmap and (
355 matchalways or matchfn(fname)):
355 matchalways or matchfn(fname)):
356 results[fname] = None
356 results[fname] = None
357 elif kind == dirkind:
357 elif kind == dirkind:
358 if fname in dmap and (matchalways or matchfn(fname)):
358 if fname in dmap and (matchalways or matchfn(fname)):
359 results[fname] = None
359 results[fname] = None
360 elif kind == regkind or kind == lnkkind:
360 elif kind == regkind or kind == lnkkind:
361 if fname in dmap:
361 if fname in dmap:
362 if matchalways or matchfn(fname):
362 if matchalways or matchfn(fname):
363 results[fname] = entry
363 results[fname] = entry
364 elif (matchalways or matchfn(fname)) and not ignore(fname):
364 elif (matchalways or matchfn(fname)) and not ignore(fname):
365 results[fname] = entry
365 results[fname] = entry
366 elif fname in dmap and (matchalways or matchfn(fname)):
366 elif fname in dmap and (matchalways or matchfn(fname)):
367 results[fname] = None
367 results[fname] = None
368
368
369 # step 3: query notable files we don't already know about
369 # step 3: query notable files we don't already know about
370 # XXX try not to iterate over the entire dmap
370 # XXX try not to iterate over the entire dmap
371 if normalize:
371 if normalize:
372 # any notable files that have changed case will already be handled
372 # any notable files that have changed case will already be handled
373 # above, so just check membership in the foldmap
373 # above, so just check membership in the foldmap
374 notefiles = set((normalize(f, True, True) for f in notefiles
374 notefiles = set((normalize(f, True, True) for f in notefiles
375 if normcase(f) not in foldmap))
375 if normcase(f) not in foldmap))
376 visit = set((f for f in notefiles if (f not in results and matchfn(f)
376 visit = set((f for f in notefiles if (f not in results and matchfn(f)
377 and (f in dmap or not ignore(f)))))
377 and (f in dmap or not ignore(f)))))
378
378
379 if nonnormalset is not None and not fresh_instance:
379 if nonnormalset is not None and not fresh_instance:
380 if matchalways:
380 if matchalways:
381 visit.update(f for f in nonnormalset if f not in results)
381 visit.update(f for f in nonnormalset if f not in results)
382 visit.update(f for f in copymap if f not in results)
382 visit.update(f for f in copymap if f not in results)
383 else:
383 else:
384 visit.update(f for f in nonnormalset
384 visit.update(f for f in nonnormalset
385 if f not in results and matchfn(f))
385 if f not in results and matchfn(f))
386 visit.update(f for f in copymap
386 visit.update(f for f in copymap
387 if f not in results and matchfn(f))
387 if f not in results and matchfn(f))
388 else:
388 else:
389 if matchalways:
389 if matchalways:
390 visit.update(f for f, st in dmap.iteritems()
390 visit.update(f for f, st in dmap.iteritems()
391 if (f not in results and
391 if (f not in results and
392 (st[2] < 0 or st[0] != 'n' or fresh_instance)))
392 (st[2] < 0 or st[0] != 'n' or fresh_instance)))
393 visit.update(f for f in copymap if f not in results)
393 visit.update(f for f in copymap if f not in results)
394 else:
394 else:
395 visit.update(f for f, st in dmap.iteritems()
395 visit.update(f for f, st in dmap.iteritems()
396 if (f not in results and
396 if (f not in results and
397 (st[2] < 0 or st[0] != 'n' or fresh_instance)
397 (st[2] < 0 or st[0] != 'n' or fresh_instance)
398 and matchfn(f)))
398 and matchfn(f)))
399 visit.update(f for f in copymap
399 visit.update(f for f in copymap
400 if f not in results and matchfn(f))
400 if f not in results and matchfn(f))
401
401
402 audit = pathutil.pathauditor(self._root, cached=True).check
402 audit = pathutil.pathauditor(self._root, cached=True).check
403 auditpass = [f for f in visit if audit(f)]
403 auditpass = [f for f in visit if audit(f)]
404 auditpass.sort()
404 auditpass.sort()
405 auditfail = visit.difference(auditpass)
405 auditfail = visit.difference(auditpass)
406 for f in auditfail:
406 for f in auditfail:
407 results[f] = None
407 results[f] = None
408
408
409 nf = iter(auditpass).next
409 nf = iter(auditpass).next
410 for st in util.statfiles([join(f) for f in auditpass]):
410 for st in util.statfiles([join(f) for f in auditpass]):
411 f = nf()
411 f = nf()
412 if st or f in dmap:
412 if st or f in dmap:
413 results[f] = st
413 results[f] = st
414
414
415 for s in subrepos:
415 for s in subrepos:
416 del results[s]
416 del results[s]
417 del results['.hg']
417 del results['.hg']
418 return results
418 return results
419
419
420 def overridestatus(
420 def overridestatus(
421 orig, self, node1='.', node2=None, match=None, ignored=False,
421 orig, self, node1='.', node2=None, match=None, ignored=False,
422 clean=False, unknown=False, listsubrepos=False):
422 clean=False, unknown=False, listsubrepos=False):
423 listignored = ignored
423 listignored = ignored
424 listclean = clean
424 listclean = clean
425 listunknown = unknown
425 listunknown = unknown
426
426
427 def _cmpsets(l1, l2):
427 def _cmpsets(l1, l2):
428 try:
428 try:
429 if 'FSMONITOR_LOG_FILE' in encoding.environ:
429 if 'FSMONITOR_LOG_FILE' in encoding.environ:
430 fn = encoding.environ['FSMONITOR_LOG_FILE']
430 fn = encoding.environ['FSMONITOR_LOG_FILE']
431 f = open(fn, 'wb')
431 f = open(fn, 'wb')
432 else:
432 else:
433 fn = 'fsmonitorfail.log'
433 fn = 'fsmonitorfail.log'
434 f = self.opener(fn, 'wb')
434 f = self.opener(fn, 'wb')
435 except (IOError, OSError):
435 except (IOError, OSError):
436 self.ui.warn(_('warning: unable to write to %s\n') % fn)
436 self.ui.warn(_('warning: unable to write to %s\n') % fn)
437 return
437 return
438
438
439 try:
439 try:
440 for i, (s1, s2) in enumerate(zip(l1, l2)):
440 for i, (s1, s2) in enumerate(zip(l1, l2)):
441 if set(s1) != set(s2):
441 if set(s1) != set(s2):
442 f.write('sets at position %d are unequal\n' % i)
442 f.write('sets at position %d are unequal\n' % i)
443 f.write('watchman returned: %s\n' % s1)
443 f.write('watchman returned: %s\n' % s1)
444 f.write('stat returned: %s\n' % s2)
444 f.write('stat returned: %s\n' % s2)
445 finally:
445 finally:
446 f.close()
446 f.close()
447
447
448 if isinstance(node1, context.changectx):
448 if isinstance(node1, context.changectx):
449 ctx1 = node1
449 ctx1 = node1
450 else:
450 else:
451 ctx1 = self[node1]
451 ctx1 = self[node1]
452 if isinstance(node2, context.changectx):
452 if isinstance(node2, context.changectx):
453 ctx2 = node2
453 ctx2 = node2
454 else:
454 else:
455 ctx2 = self[node2]
455 ctx2 = self[node2]
456
456
457 working = ctx2.rev() is None
457 working = ctx2.rev() is None
458 parentworking = working and ctx1 == self['.']
458 parentworking = working and ctx1 == self['.']
459 match = match or matchmod.always(self.root, self.getcwd())
459 match = match or matchmod.always(self.root, self.getcwd())
460
460
461 # Maybe we can use this opportunity to update Watchman's state.
461 # Maybe we can use this opportunity to update Watchman's state.
462 # Mercurial uses workingcommitctx and/or memctx to represent the part of
462 # Mercurial uses workingcommitctx and/or memctx to represent the part of
463 # the workingctx that is to be committed. So don't update the state in
463 # the workingctx that is to be committed. So don't update the state in
464 # that case.
464 # that case.
465 # HG_PENDING is set in the environment when the dirstate is being updated
465 # HG_PENDING is set in the environment when the dirstate is being updated
466 # in the middle of a transaction; we must not update our state in that
466 # in the middle of a transaction; we must not update our state in that
467 # case, or we risk forgetting about changes in the working copy.
467 # case, or we risk forgetting about changes in the working copy.
468 updatestate = (parentworking and match.always() and
468 updatestate = (parentworking and match.always() and
469 not isinstance(ctx2, (context.workingcommitctx,
469 not isinstance(ctx2, (context.workingcommitctx,
470 context.memctx)) and
470 context.memctx)) and
471 'HG_PENDING' not in encoding.environ)
471 'HG_PENDING' not in encoding.environ)
472
472
473 try:
473 try:
474 if self._fsmonitorstate.walk_on_invalidate:
474 if self._fsmonitorstate.walk_on_invalidate:
475 # Use a short timeout to query the current clock. If that
475 # Use a short timeout to query the current clock. If that
476 # takes too long then we assume that the service will be slow
476 # takes too long then we assume that the service will be slow
477 # to answer our query.
477 # to answer our query.
478 # walk_on_invalidate indicates that we prefer to walk the
478 # walk_on_invalidate indicates that we prefer to walk the
479 # tree ourselves because we can ignore portions that Watchman
479 # tree ourselves because we can ignore portions that Watchman
480 # cannot and we tend to be faster in the warmer buffer cache
480 # cannot and we tend to be faster in the warmer buffer cache
481 # cases.
481 # cases.
482 self._watchmanclient.settimeout(0.1)
482 self._watchmanclient.settimeout(0.1)
483 else:
483 else:
484 # Give Watchman more time to potentially complete its walk
484 # Give Watchman more time to potentially complete its walk
485 # and return the initial clock. In this mode we assume that
485 # and return the initial clock. In this mode we assume that
486 # the filesystem will be slower than parsing a potentially
486 # the filesystem will be slower than parsing a potentially
487 # very large Watchman result set.
487 # very large Watchman result set.
488 self._watchmanclient.settimeout(
488 self._watchmanclient.settimeout(
489 self._fsmonitorstate.timeout + 0.1)
489 self._fsmonitorstate.timeout + 0.1)
490 startclock = self._watchmanclient.getcurrentclock()
490 startclock = self._watchmanclient.getcurrentclock()
491 except Exception as ex:
491 except Exception as ex:
492 self._watchmanclient.clearconnection()
492 self._watchmanclient.clearconnection()
493 _handleunavailable(self.ui, self._fsmonitorstate, ex)
493 _handleunavailable(self.ui, self._fsmonitorstate, ex)
494 # boo, Watchman failed. bail
494 # boo, Watchman failed. bail
495 return orig(node1, node2, match, listignored, listclean,
495 return orig(node1, node2, match, listignored, listclean,
496 listunknown, listsubrepos)
496 listunknown, listsubrepos)
497
497
498 if updatestate:
498 if updatestate:
499 # We need info about unknown files. This may make things slower the
499 # We need info about unknown files. This may make things slower the
500 # first time, but whatever.
500 # first time, but whatever.
501 stateunknown = True
501 stateunknown = True
502 else:
502 else:
503 stateunknown = listunknown
503 stateunknown = listunknown
504
504
505 if updatestate:
505 if updatestate:
506 ps = poststatus(startclock)
506 ps = poststatus(startclock)
507 self.addpostdsstatus(ps)
507 self.addpostdsstatus(ps)
508
508
509 r = orig(node1, node2, match, listignored, listclean, stateunknown,
509 r = orig(node1, node2, match, listignored, listclean, stateunknown,
510 listsubrepos)
510 listsubrepos)
511 modified, added, removed, deleted, unknown, ignored, clean = r
511 modified, added, removed, deleted, unknown, ignored, clean = r
512
512
513 if not listunknown:
513 if not listunknown:
514 unknown = []
514 unknown = []
515
515
516 # don't do paranoid checks if we're not going to query Watchman anyway
516 # don't do paranoid checks if we're not going to query Watchman anyway
517 full = listclean or match.traversedir is not None
517 full = listclean or match.traversedir is not None
518 if self._fsmonitorstate.mode == 'paranoid' and not full:
518 if self._fsmonitorstate.mode == 'paranoid' and not full:
519 # run status again and fall back to the old walk this time
519 # run status again and fall back to the old walk this time
520 self.dirstate._fsmonitordisable = True
520 self.dirstate._fsmonitordisable = True
521
521
522 # shut the UI up
522 # shut the UI up
523 quiet = self.ui.quiet
523 quiet = self.ui.quiet
524 self.ui.quiet = True
524 self.ui.quiet = True
525 fout, ferr = self.ui.fout, self.ui.ferr
525 fout, ferr = self.ui.fout, self.ui.ferr
526 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
526 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
527
527
528 try:
528 try:
529 rv2 = orig(
529 rv2 = orig(
530 node1, node2, match, listignored, listclean, listunknown,
530 node1, node2, match, listignored, listclean, listunknown,
531 listsubrepos)
531 listsubrepos)
532 finally:
532 finally:
533 self.dirstate._fsmonitordisable = False
533 self.dirstate._fsmonitordisable = False
534 self.ui.quiet = quiet
534 self.ui.quiet = quiet
535 self.ui.fout, self.ui.ferr = fout, ferr
535 self.ui.fout, self.ui.ferr = fout, ferr
536
536
537 # clean isn't tested since it's set to True above
537 # clean isn't tested since it's set to True above
538 _cmpsets([modified, added, removed, deleted, unknown, ignored, clean],
538 _cmpsets([modified, added, removed, deleted, unknown, ignored, clean],
539 rv2)
539 rv2)
540 modified, added, removed, deleted, unknown, ignored, clean = rv2
540 modified, added, removed, deleted, unknown, ignored, clean = rv2
541
541
542 return scmutil.status(
542 return scmutil.status(
543 modified, added, removed, deleted, unknown, ignored, clean)
543 modified, added, removed, deleted, unknown, ignored, clean)
544
544
545 class poststatus(object):
545 class poststatus(object):
546 def __init__(self, startclock):
546 def __init__(self, startclock):
547 self._startclock = startclock
547 self._startclock = startclock
548
548
549 def __call__(self, wctx, status):
549 def __call__(self, wctx, status):
550 clock = wctx.repo()._fsmonitorstate.getlastclock() or self._startclock
550 clock = wctx.repo()._fsmonitorstate.getlastclock() or self._startclock
551 hashignore = _hashignore(wctx.repo().dirstate._ignore)
551 hashignore = _hashignore(wctx.repo().dirstate._ignore)
552 notefiles = (status.modified + status.added + status.removed +
552 notefiles = (status.modified + status.added + status.removed +
553 status.deleted + status.unknown)
553 status.deleted + status.unknown)
554 wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
554 wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
555
555
556 def makedirstate(repo, dirstate):
556 def makedirstate(repo, dirstate):
557 class fsmonitordirstate(dirstate.__class__):
557 class fsmonitordirstate(dirstate.__class__):
558 def _fsmonitorinit(self, fsmonitorstate, watchmanclient):
558 def _fsmonitorinit(self, fsmonitorstate, watchmanclient):
559 # _fsmonitordisable is used in paranoid mode
559 # _fsmonitordisable is used in paranoid mode
560 self._fsmonitordisable = False
560 self._fsmonitordisable = False
561 self._fsmonitorstate = fsmonitorstate
561 self._fsmonitorstate = fsmonitorstate
562 self._watchmanclient = watchmanclient
562 self._watchmanclient = watchmanclient
563
563
564 def walk(self, *args, **kwargs):
564 def walk(self, *args, **kwargs):
565 orig = super(fsmonitordirstate, self).walk
565 orig = super(fsmonitordirstate, self).walk
566 if self._fsmonitordisable:
566 if self._fsmonitordisable:
567 return orig(*args, **kwargs)
567 return orig(*args, **kwargs)
568 return overridewalk(orig, self, *args, **kwargs)
568 return overridewalk(orig, self, *args, **kwargs)
569
569
570 def rebuild(self, *args, **kwargs):
570 def rebuild(self, *args, **kwargs):
571 self._fsmonitorstate.invalidate()
571 self._fsmonitorstate.invalidate()
572 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
572 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
573
573
574 def invalidate(self, *args, **kwargs):
574 def invalidate(self, *args, **kwargs):
575 self._fsmonitorstate.invalidate()
575 self._fsmonitorstate.invalidate()
576 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
576 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
577
577
578 dirstate.__class__ = fsmonitordirstate
578 dirstate.__class__ = fsmonitordirstate
579 dirstate._fsmonitorinit(repo._fsmonitorstate, repo._watchmanclient)
579 dirstate._fsmonitorinit(repo._fsmonitorstate, repo._watchmanclient)
580
580
581 def wrapdirstate(orig, self):
581 def wrapdirstate(orig, self):
582 ds = orig(self)
582 ds = orig(self)
583 # only override the dirstate when Watchman is available for the repo
583 # only override the dirstate when Watchman is available for the repo
584 if util.safehasattr(self, '_fsmonitorstate'):
584 if util.safehasattr(self, '_fsmonitorstate'):
585 makedirstate(self, ds)
585 makedirstate(self, ds)
586 return ds
586 return ds
587
587
588 def extsetup(ui):
588 def extsetup(ui):
589 extensions.wrapfilecache(
589 extensions.wrapfilecache(
590 localrepo.localrepository, 'dirstate', wrapdirstate)
590 localrepo.localrepository, 'dirstate', wrapdirstate)
591 if pycompat.sysplatform == 'darwin':
591 if pycompat.sysplatform == 'darwin':
592 # An assist for avoiding the dangling-symlink fsevents bug
592 # An assist for avoiding the dangling-symlink fsevents bug
593 extensions.wrapfunction(os, 'symlink', wrapsymlink)
593 extensions.wrapfunction(os, 'symlink', wrapsymlink)
594
594
595 extensions.wrapfunction(merge, 'update', wrapupdate)
595 extensions.wrapfunction(merge, 'update', wrapupdate)
596
596
597 def wrapsymlink(orig, source, link_name):
597 def wrapsymlink(orig, source, link_name):
598 ''' if we create a dangling symlink, also touch the parent dir
598 ''' if we create a dangling symlink, also touch the parent dir
599 to encourage fsevents notifications to work more correctly '''
599 to encourage fsevents notifications to work more correctly '''
600 try:
600 try:
601 return orig(source, link_name)
601 return orig(source, link_name)
602 finally:
602 finally:
603 try:
603 try:
604 os.utime(os.path.dirname(link_name), None)
604 os.utime(os.path.dirname(link_name), None)
605 except OSError:
605 except OSError:
606 pass
606 pass
607
607
608 class state_update(object):
608 class state_update(object):
609 ''' This context manager is responsible for dispatching the state-enter
609 ''' This context manager is responsible for dispatching the state-enter
610 and state-leave signals to the watchman service '''
610 and state-leave signals to the watchman service '''
611
611
612 def __init__(self, repo, node, distance, partial):
612 def __init__(self, repo, node, distance, partial):
613 self.repo = repo
613 self.repo = repo
614 self.node = node
614 self.node = node
615 self.distance = distance
615 self.distance = distance
616 self.partial = partial
616 self.partial = partial
617 self._lock = None
617 self._lock = None
618 self.need_leave = False
618 self.need_leave = False
619
619
620 def __enter__(self):
620 def __enter__(self):
621 # We explicitly need to take a lock here, before we proceed to update
621 # We explicitly need to take a lock here, before we proceed to update
622 # watchman about the update operation, so that we don't race with
622 # watchman about the update operation, so that we don't race with
623 # some other actor. merge.update is going to take the wlock almost
623 # some other actor. merge.update is going to take the wlock almost
624 # immediately anyway, so this is effectively extending the lock
624 # immediately anyway, so this is effectively extending the lock
625 # around a couple of short sanity checks.
625 # around a couple of short sanity checks.
626 self._lock = self.repo.wlock()
626 self._lock = self.repo.wlock()
627 self.need_leave = self._state('state-enter')
627 self.need_leave = self._state('state-enter')
628 return self
628 return self
629
629
630 def __exit__(self, type_, value, tb):
630 def __exit__(self, type_, value, tb):
631 try:
631 try:
632 if self.need_leave:
632 if self.need_leave:
633 status = 'ok' if type_ is None else 'failed'
633 status = 'ok' if type_ is None else 'failed'
634 self._state('state-leave', status=status)
634 self._state('state-leave', status=status)
635 finally:
635 finally:
636 if self._lock:
636 if self._lock:
637 self._lock.release()
637 self._lock.release()
638
638
639 def _state(self, cmd, status='ok'):
639 def _state(self, cmd, status='ok'):
640 if not util.safehasattr(self.repo, '_watchmanclient'):
640 if not util.safehasattr(self.repo, '_watchmanclient'):
641 return False
641 return False
642 try:
642 try:
643 commithash = self.repo[self.node].hex()
643 commithash = self.repo[self.node].hex()
644 self.repo._watchmanclient.command(cmd, {
644 self.repo._watchmanclient.command(cmd, {
645 'name': 'hg.update',
645 'name': 'hg.update',
646 'metadata': {
646 'metadata': {
647 # the target revision
647 # the target revision
648 'rev': commithash,
648 'rev': commithash,
649 # approximate number of commits between current and target
649 # approximate number of commits between current and target
650 'distance': self.distance,
650 'distance': self.distance,
651 # success/failure (only really meaningful for state-leave)
651 # success/failure (only really meaningful for state-leave)
652 'status': status,
652 'status': status,
653 # whether the working copy parent is changing
653 # whether the working copy parent is changing
654 'partial': self.partial,
654 'partial': self.partial,
655 }})
655 }})
656 return True
656 return True
657 except Exception as e:
657 except Exception as e:
658 # Swallow any errors; fire and forget
658 # Swallow any errors; fire and forget
659 self.repo.ui.log(
659 self.repo.ui.log(
660 'watchman', 'Exception %s while running %s\n', e, cmd)
660 'watchman', 'Exception %s while running %s\n', e, cmd)
661 return False
661 return False
662
662
663 # Estimate the distance between two nodes
664 def calcdistance(repo, oldnode, newnode):
665 anc = repo.changelog.ancestor(oldnode, newnode)
666 ancrev = repo[anc].rev()
667 distance = (abs(repo[oldnode].rev() - ancrev)
668 + abs(repo[newnode].rev() - ancrev))
669 return distance
670
663 # Bracket working copy updates with calls to the watchman state-enter
671 # Bracket working copy updates with calls to the watchman state-enter
664 # and state-leave commands. This allows clients to perform more intelligent
672 # and state-leave commands. This allows clients to perform more intelligent
665 # settling during bulk file change scenarios
673 # settling during bulk file change scenarios
666 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
674 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
667 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
675 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
668 mergeancestor=False, labels=None, matcher=None, **kwargs):
676 mergeancestor=False, labels=None, matcher=None, **kwargs):
669
677
670 distance = 0
678 distance = 0
671 partial = True
679 partial = True
672 if matcher is None or matcher.always():
680 if matcher is None or matcher.always():
673 partial = False
681 partial = False
674 wc = repo[None]
682 distance = calcdistance(repo.unfiltered(), repo['.'].node(),
675 parents = wc.parents()
683 repo[node].node())
676 if len(parents) == 2:
677 anc = repo.changelog.ancestor(parents[0].node(), parents[1].node())
678 ancrev = repo[anc].rev()
679 distance = abs(repo[node].rev() - ancrev)
680 elif len(parents) == 1:
681 distance = abs(repo[node].rev() - parents[0].rev())
682
684
683 with state_update(repo, node, distance, partial):
685 with state_update(repo, node, distance, partial):
684 return orig(
686 return orig(
685 repo, node, branchmerge, force, ancestor, mergeancestor,
687 repo, node, branchmerge, force, ancestor, mergeancestor,
686 labels, matcher, **kwargs)
688 labels, matcher, **kwargs)
687
689
688 def reposetup(ui, repo):
690 def reposetup(ui, repo):
689 # We don't work with largefiles or inotify
691 # We don't work with largefiles or inotify
690 exts = extensions.enabled()
692 exts = extensions.enabled()
691 for ext in _blacklist:
693 for ext in _blacklist:
692 if ext in exts:
694 if ext in exts:
693 ui.warn(_('The fsmonitor extension is incompatible with the %s '
695 ui.warn(_('The fsmonitor extension is incompatible with the %s '
694 'extension and has been disabled.\n') % ext)
696 'extension and has been disabled.\n') % ext)
695 return
697 return
696
698
697 if repo.local():
699 if repo.local():
698 # We don't work with subrepos either.
700 # We don't work with subrepos either.
699 #
701 #
700 # if repo[None].substate can cause a dirstate parse, which is too
702 # if repo[None].substate can cause a dirstate parse, which is too
701 # slow. Instead, look for a file called hgsubstate,
703 # slow. Instead, look for a file called hgsubstate,
702 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
704 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
703 return
705 return
704
706
705 fsmonitorstate = state.state(repo)
707 fsmonitorstate = state.state(repo)
706 if fsmonitorstate.mode == 'off':
708 if fsmonitorstate.mode == 'off':
707 return
709 return
708
710
709 try:
711 try:
710 client = watchmanclient.client(repo)
712 client = watchmanclient.client(repo)
711 except Exception as ex:
713 except Exception as ex:
712 _handleunavailable(ui, fsmonitorstate, ex)
714 _handleunavailable(ui, fsmonitorstate, ex)
713 return
715 return
714
716
715 repo._fsmonitorstate = fsmonitorstate
717 repo._fsmonitorstate = fsmonitorstate
716 repo._watchmanclient = client
718 repo._watchmanclient = client
717
719
718 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
720 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
719 if cached:
721 if cached:
720 # at this point since fsmonitorstate wasn't present,
722 # at this point since fsmonitorstate wasn't present,
721 # repo.dirstate is not a fsmonitordirstate
723 # repo.dirstate is not a fsmonitordirstate
722 makedirstate(repo, dirstate)
724 makedirstate(repo, dirstate)
723
725
724 class fsmonitorrepo(repo.__class__):
726 class fsmonitorrepo(repo.__class__):
725 def status(self, *args, **kwargs):
727 def status(self, *args, **kwargs):
726 orig = super(fsmonitorrepo, self).status
728 orig = super(fsmonitorrepo, self).status
727 return overridestatus(orig, self, *args, **kwargs)
729 return overridestatus(orig, self, *args, **kwargs)
728
730
729 repo.__class__ = fsmonitorrepo
731 repo.__class__ = fsmonitorrepo
General Comments 0
You need to be logged in to leave comments. Login now