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