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