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