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