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